Vue project performance optimization solution

1. Optimization at the code level

 1. Scenarios for using v-if and v-show
 (1) v-if is suitable for scenarios that rarely change conditions at runtime and do not need to switch conditions frequently. It is a true conditional rendering and will ensure that elements Appropriately being destroyed and rebuilt is also lazy;
 (2) v-show is suitable for scenes with frequent switching conditions, v-show elements will always be rendered regardless of the initial rendering conditions, and then according to the display property of css Value to toggle between showing and hiding.

2. Scenarios for using computed and watch
 (1) When numerical calculations are required and depend on other data, computed should be used, and the cache feature of computed can be used to avoid recalculation every time a value is obtained; (2)
 when When we need to perform asynchronous or expensive operations when data changes, we should use watch, which allows us to perform asynchronous operations, limits the frequency of our operations, and sets intermediate values ​​before we get the final result. To each current value and previous value.

 3. The key must be added to the item for v-for loop traversal. It is not recommended to set the key as an index, and avoid using v-if in the same element at the same time

4. Long list performance optimization
 Vue will hijack the data through Object.defineProperty() to realize the change of response data, but sometimes our components only need to realize the display of data, and there will be no other modification and deletion Operation, so there is no need for vue to hijack our data. In the case of a large amount of data display, this can significantly reduce the initialization time of the component. You can freeze an object through the Object.freeze() method. Once the object is frozen cannot be modified

5. Lazy loading of image resources: use the vue-lazyload plug-
in Installation: npm install vue-lazyload --save-dev
main.js Introduction: import VueLazyload from 'vue-lazyload'
Use: Vue.use (VueLazyload)
page, directly Change the src attribute of img to v-lazy: <img v-lazy="/static/img/logo.png">

6. Route lazy loading: const comp = () => import('./Comp.vue')

7. On-demand introduction of third-party plug-ins

8. Vue optimizes infinite list performance: use the vue-virtual-scroll-list plugin
to install: npm install vue-virtual-scroll-list --save-dev
use: <virtual-list style="height: 660px; overflow-y: auto;" :data-key="'id'" :data-sources="itemData" :data-component="itemComponent" /> (itemData: data, itemConponent: the component that actually displays the content)

9. The use of keep-alive Use
keep-alive to cache inactive components. During the price switch process, the state of the components is saved in memory to prevent repeated rendering of dom, reduce loading time and performance consumption, and improve user experience .

2. Optimization at the webpack level

1. Compress the image
In the vue project, you can set the limit size in the url-loader in webpack.base.conf.js to process the image, convert the image smaller than the limit to base64 format, and do nothing for the rest. So for some larger image resources, when requesting resources, the loading will be very slow, we can use image-webpack-loader to compress the image; installation: npm
install image-webpack-loader --save-dev
webpack.js Configuration:

{
  test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
  use:[
    {
    loader: 'url-loader',
    options: {
      limit: 10000,
      name: utils.assetsPath('img/[name].[hash:7].[ext]')
      }
    },
    {
      loader: 'image-webpack-loader',
      options: {
        bypassOnDebug: true,
      }
    }
  ]
}


2. Reduce redundant code from es6 to es5
The Babel plugin will inject some auxiliary functions when converting es6 code to es5 code, for example: class HelloWebpack extends Component{...}, this code will be converted to run normally The following two helper functions are required for es5 code:

babel-runtime/helpers/createClass  // 用于实现 class 语法
babel-runtime/helpers/inherits  // 用于实现 extends 语法


By default, Babel will embed these dependent auxiliary function codes in each output file. If multiple source code files depend on these auxiliary functions, the code of these auxiliary functions will appear many times, resulting in redundant code. Yu, in order not to repeat them, you can import them by require('babel-runtime/helpers/createClass') when relying on them, so that they only appear once, the plugin babel-plugin-transform-runtime is To achieve this function, replace the relevant auxiliary functions with import statements, thereby reducing the size of the code compiled by babel;
installation: npm install babel-plugin-transform-runtime --save-dev
to modify the .babelrc configuration file :

"plugins": [
    "transform-runtime"
]


3. Extract common code, separate business code from third-party library code: use CommonsChunkPlugin plug-in in webpack3, use optimization.splitChunks plug-in in webpack4

CommonsChunkPlugin plug-in detailed parameters:
name: name the chunk containing common code
filename: name the js file generated after packaging
minChunks: the judgment standard of common code, how many files a certain js module is imported into is considered common code, the default is 1, We can be 2, that is to say, if
a file is introduced by other pages more than 2 times or more, it can be considered that the file is a public code.
chunks: Indicates which chunks (each entry in the configuration) need to find common codes for packaging. If it is not set by default, then its search range is all chunks;

If the project does not extract the third-party library and public modules of each page, the project will have the following problems: a
. The same resources are loaded repeatedly, wasting customer traffic and server costs
b. Each page needs to be loaded Too many resources lead to slow loading of the first screen of the webpage and affect the user experience.
Therefore, we need to separate the common codes of multiple pages into separate files to optimize the above problems. Webpack has a built-in tool for extracting the common parts of multiple chunks. The plugin CommonsChunkPlugin, our configuration of CommonChunkPlugin in the project is as follows:

// 所有在 package.json 里面依赖的包,都会被打包进 vendor.js 这个文件中。
new webpack.optimize.CommonsChunkPlugin({
  name: 'vendor',
  minChunks: function(module, count) {
    return (
      module.resource &&
      /\.js$/.test(module.resource) &&
      module.resource.indexOf(
        path.join(__dirname, '../node_modules')
      ) === 0
    );
  }
}),
// 抽取出代码模块的映射关系
new webpack.optimize.CommonsChunkPlugin({
  name: 'manifest',
  chunks: ['vendor']
})

Optimization.splitChunks parameter details:

(1), cacheGroups: the core of splitChunks configuration, the code splitting rules are all configured in the cacheGroups cache group, each attribute in the cache group is a configuration rule, you can define the attribute name yourself, and the attribute value is an object, It is a description of a code splitting rule, which has two attributes: name and chunks;
name: the extracted public module will be named after this name, which can be left unconfigured, if not configured, a default file will be generated Name
chunks: specify which types of chunks participate in the split, the value can be a string or a function, if it is a string, it can have three values: all, async, initial, all represents all modules, async represents asynchronous loading, and initial represents The module that can be obtained during initialization. If it is a function, it can be filtered in more detail through the name attribute of chunks, etc.

(2), miniChunks: splitChunks comes with default configurations, and the cache group will inherit these configurations by default. There is a miniChunks attribute, which controls when each module is extracted: when a module is referenced by a different entry When the number of times is greater than or equal to the value of this configuration, it will be extracted. The default value is 1, that is, any module will be extracted. (The entry module will also be imported once by webpack)

(3), miniSize: the minimum size of the extracted chunk

(4), priority: the value is a numeric value, which can be a negative number. The function is that when multiple split rules are set in the split group, and a module conforms to multiple rules, the priority attribute priority is used to determine which split to use Sub-rules, the one with the higher priority will be executed

(5), test: the module used to further control the selection of the cache group, which can be a regular expression or a function, which can match the absolute path of the module or the chunk name. When matching the chunk name, all the modules in the chunk will be selected

example:

optimization: {
    splitChunks: {
        minSize: 30,  //提取出的chunk的最小大小
        cacheGroups: {
            default: {
                name: 'common',
                chunks: 'initial',
                minChunks: 2,  //模块被引用2次以上的才抽离
                priority: -20
            },
            vendors: {  //拆分第三方库(通过npm|yarn安装的库)
                test: /[\\/]node_modules[\\/]/,
                name: 'vendor',
                chunks: 'initial',
                priority: -10
            }
        }
    }
}

4. Output analysis of build results:
The code output by webpack-bundle-analyzer webpack is very readable, and the file is very large. In order to analyze the output results simply and intuitively, you can use analysis tools to visualize the results more intuitively in the form of graphics Show it
Install: npm install --save-dev webpack-bundle-analyzer
webpack.config.js configuration:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
plugins: [
    new BundleAnalyzerPlugin()
    ]


Add in "scripts" of package.json:

"dev": "webpack --config webpack.config.js --progress"


Execute statement to generate analysis report: npm run dev

5. Package and remove console.log and other statements: uglifyjs-webpack-plugin
installation: npm install uglifyjs-webpack-plugin
configuration in webpack.config.js:

const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
    configureWebpack: (config) => {
        if (process.env.NODE_ENV === 'production') {
            return {
                plugins: [
                    //打包环境去掉console.log
                    new UglifyJsPlugin({
                        uglifyOptions: {
                            compress: {
                                warnings: false,
                                drop_console: true,  //注释console
                                drop_debugger: true, //注释debugger
                                pure_funcs: ['console.log'], //移除console.log
                            },
                        },
                    }),
                ],
            }
        }
    }
}

3. Basic web technology optimization

1. Enable gzip compression: compression-webpack-plugin
installation: //The new version is not compatible, recommend this version
npm install [email protected] --save-dev
Use in webpack.config.js:

const CompressionWebpackPlugin = require('compression-webpack-plugin')
new CompressionWebpackPlugin({
      filename: '[path][base].gz',
      algorithm: 'gzip',  // 压缩算法,官方默认压缩算法是gzip
      test: /\.js$|\.css$|\.html$|\.eot$|\.woff$/, // 使用gzip压缩的文件类型
      threshold: 10240,  // 以字节为单位压缩超过此大小的文件,默认是10240
      minRatio: 0.8,  // 最小压缩比率,默认是0.8
    })


Back-end configuration:
(1) Nginx: Mainly the gizp configuration below, just copy and paste it to use it, the personal test is effective

server{
    //开启和关闭gzip模式
    gzip on;
    //gizp压缩起点,文件大于2k才进行压缩;设置允许压缩的页面最小字节数,页面字节数从header头得content-length中进行获取。 默认值是0,不管页面多大都压缩。建议设置成大于2k的字节数,小于2k可能会越压越大。
    gzip_min_length 2k;
    // 设置压缩所需要的缓冲区大小,以4k为单位,如果文件为7k则申请2*4k的缓冲区 
    gzip_buffers 4 16k;
    // 设置gzip压缩针对的HTTP协议版本
    gzip_http_version 1.0;
    // gzip 压缩级别,1-9,数字越大压缩的越好,也越占用CPU时间
    gzip_comp_level 2;
    //进行压缩的文件类型
    gzip_types text/plain application/javascript text/css application/xml;
    // 是否在http header中添加Vary: Accept-Encoding,建议开启
    gzip_vary on;
}


(2) Express middleware: compression, mainly used for gzip compression, usually used for web performance optimization, can compress text content, generally used for html files; installation: npm install compression --save
use
:

var compression = require('compression');
const app = express()
// 启用gzip
app.use(compression());

2. Browser cache Cache
static resources, classify according to whether it needs to re-initiate a request to the server, and divide http requests into mandatory cache and contrast cache

3. When CDN uses
a browser to download files such as css, js, and pictures from the server, it must be connected to the server, and most servers have limited bandwidth. Different domain names are used to load files, so that the number of concurrent connections for downloading files is greatly increased, and CDN has better availability, lower network delay and packet loss rate

4. Use Chrome Performance to find performance bottlenecks.
Chrome’s Performance panel can record js execution details and time for a period of time. Steps:
a. Open the Chrome developer tools, click on the Performance panel
b. Click Record to start recording
c. Refresh the page or expand A node
d, click stop to stop recording, you can view it

4. Optimization at the network level

1. Cancellation of repeated requests
When the second request is made before the first request comes back, the second request should be canceled to prevent repeated requests; encapsulate it
in the axios request and use an interceptor Processing, first implement three methods. When the request method, request URL and carried parameters are all consistent, it can be considered as the same request. Therefore, when initiating a request, it can be generated according to the current request method, request address and carried parameters A unique key, and create a dedicated cancelToken for each request, and then save the key and cancelToken functions in the map object in the form of key-value pairs. The advantage of using the map object is that you can quickly determine whether there are duplicate requests:

    // 用于根据当前的请求信息生成请求的key
    generateReqKey(config) {
      const { url, method, params, data } = config;
      return [url, method, qs.stringify(params), qs.stringify(data)].join("&");
    },
    // 用于将当前请求信息添加到pendingRequest对象中
    PendingRequest = new Map(),
    addPendingRequest(config) {
      const requestKey = this.generateReqKey(config)
      config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
        if(!this.PendingRequest.has(requestKey)) {
          this.PendingRequest.set(requestKey, cancel)
        }
      })
    },
    // 检查是否有重复的请求,若存在则需要取消已经发出的请求
    removeRequest(config) {
      const requestKey = this.generateReqKey(config)
      if (this.PendingRequest.has(requestKey)) {
        const CancelToken = this.PendingRequest.get(requestKey)
        CancelToken(requestKey)
        this.PendingRequest.delete(requestKey)
      }
    }

Because all requests need to be processed, the operation of canceling repeated requests is implemented directly in the interceptor,
(1) Request interceptor: the function is to perform certain operations uniformly before the request is sent, such as adding a token field in the request header
(2) Response interceptor: the function is to perform certain operations uniformly after receiving the server response, such as performing different response operations according to different status codes

    axios.interceptors.request.use(config => {
      // 检查是否有重复的请求
      this.removeRequest(config)
      // 把当前请求添加到PendingRequest对象中
      this.addPendingRequest(config)
      const token = localStorage.getItem('token')
      if(token) {
        config.headers.Authorization = `Bearer ${token}`
      }
      return config
    }, error => {
      return Promise.reject(error)
    })
    axios.interceptors.response.use(res => {
      // 从PendingRequest对象中移除请求
      this.removeRequest(res.config)
      const {data} = config;
      return data
    }, error => {
      // 从PendingRequest对象中移除请求
      this.removeRequest(error.config)
      return Promise.reject(error)
    })

2. Limit the number of failed request retries to avoid too many invalid requests, resulting in a large amount of resource consumption

//在main.js设置全局的请求次数,请求的间隙
axios.defaults.retry = 4;  // 请求次数
axios.defaults.retryDelay = 1000;  // 请求间隙
//响应拦截
axios.interceptors.response.use(undefined, function axiosRetryInterceptor(err) {
    var config = err.config;
    // 如果配置不存在或未设置重试选项,则返回错误信息
    if(!config || !config.retry) return Promise.reject(err);
 
    // 设置变量即跟踪重试次数
    config.__retryCount = config.__retryCount || 0;
 
    // 检查我们是否已经超过了总重试次数
    if(config.__retryCount >= config.retry) {
        // 返回错误信息
        return Promise.reject(err);
    }
 
    // 重试次数加1
    config.__retryCount += 1;
 
    // 创建延时器等待发送重试请求
    var backoff = new Promise(function(resolve) {
        setTimeout(function() {
            resolve();
        }, config.retryDelay || 1);
    });
 
    // 返回调用AXIOS来重试请求
    return backoff.then(function() {
        return axios(config);
    });
});

3. Make good use of the cache to reduce the number of network requests
4. Disconnection processing:
store the state of the network in vuex, and judge whether to load the disconnection component according to the state of the network. If the network is disconnected, load the disconnection component instead of loading the corresponding The page component, click refresh, jump to the refresh page and then return immediately to re-acquire data, so create a new refresh.vue and return to the current page in the beforeRouteEnter hook
 

// 断网页面
<template>  
    <div id="app">    
        <div v-if="!network">      
            <h3>我没网了</h3>      
            <div @click="onRefresh">刷新</div>      
        </div>    
        <router-view/>      
    </div>
</template>

<script>
    import { mapState } from 'vuex';
    export default {  
        name: 'App',  
        computed: {    
            ...mapState(['network'])  
        },  
        methods: {    
            // 通过跳转一个空页面再返回的方式来实现刷新当前页面数据的目的
            onRefresh () {      
                this.$router.replace('/refresh')    
            }  
        }
    }
</script>
在refresh.vue页面中:
// refresh.vue
beforeRouteEnter (to, from, next) {
    next(vm => {            
        vm.$router.replace(from.fullPath)        
    })    
}

Guess you like

Origin blog.csdn.net/m0_46318298/article/details/130380296