Performance optimization is a problem that every developer will encounter, especially in the environment where more and more attention is paid to experience and competition is becoming more and more fierce. For our developers, it is not enough to only complete iterations and do a good job in functions. The most important thing is to do a good job in products so that more people are willing to use them and users can use them better, This is also the embodiment of the value and ability of our developers.
Paying attention to performance issues and optimizing the product experience is much more valuable than changing a few irrelevant bug s
This article has recorded some tips in my daily development of Vue project. No more nonsense. Let's start
1. Long list performance optimization
1. No response
For example, member lists and commodity lists are just pure data display. In scenes where there will be no dynamic changes, there is no need to respond to the data, which can greatly improve the rendering speed
For example, use object Freeze () freezes an object. The description of MDN is that the object frozen by this method cannot be modified; That is, you cannot add new attributes to this object, delete existing attributes, modify the enumerability, configurability and Writeability of existing attributes of this object, modify the value of existing attributes, and modify the prototype of this object
export default { data: () => ({ userList: [] }), async created() { const users = await axios.get("/api/users"); this.userList = Object.freeze(users); } };
Vue2's responsive source code address: Src / core / observer / index Line JS - 144} is this
export function defineReactive (...){ const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } ... }
You can see that the direct return that determines "configurable" as "false" at the beginning will not be processed in a responsive manner
If "configurable" is "false", it means that this attribute cannot be modified, and the "configurable" of frozen objects is "false"
Vue3 adds a responsive flag to mark the target object type
2. Virtual scrolling
If it is a long list of big data, it will be very difficult to create too many DOM at one time if all of them are rendered. At this time, you can use virtual scrolling to render only a small part of the content of the area (including the visual area), and then when scrolling, constantly replace the content of the visual area to simulate the effect of scrolling
<recycle-scroller class="items" :items="items" :item-size="24" > <template v-slot="{ item }"> <FetchItemView :item="item" @vote="voteItem(item)" /> </template> </recycle-scroller>
Refer to Vue virtual scroller and Vue virtual scroll list
The principle is to monitor scrolling events, dynamically update the DOM to be displayed, and calculate the displacement in the view, which also means that real-time calculation is required in the scrolling process, which has a certain cost. Therefore, if the amount of data is not large, ordinary scrolling is OK
2. Avoid using v-if for traversal at the same time
Why avoid using , v-for , and , v-if at the same time
In Vue2, the priority of "v-for" is higher, so all the list elements will be traversed to generate virtual DOM during compilation, and then the qualified virtual DOM will be rendered through v-if judgment, which will cause a waste of performance, because we hope that the unqualified virtual DOM will not be generated
In Vue3, v-if has higher priority, which means that if the judgment condition is the attribute in the list traversed by v-for, v-if cannot be obtained
Therefore, in some scenarios that need to be used at the same time, you can filter the list by calculating attributes, as shown below
<template> <ul> <li v-for="item in activeList" :key="item.id"> {{ item.title }} </li> </ul> </template> <script> // Vue2.x export default { computed: { activeList() { return this.list.filter( item => { return item.isActive }) } } } // Vue3 import { computed } from "vue"; const activeList = computed(() => { return list.filter( item => { return item.isActive }) }) </script>
3. The list uses a unique key
For example, in a list, we need to insert an element in the middle. What will happen if we don't use key or use index as key? Look at a picture first
As shown in the figure, li1 and li2 will not be re rendered, which is not controversial. And li3, li4, li5 , will be re rendered
When you do not use , key , or , index , of the list as , key , the corresponding positional relationship of each element is index. The results in the above figure directly lead to the change of the corresponding positional relationship from the inserted elements to all the subsequent elements. Therefore, all of them will be updated and re rendered in the process of patch.
This is not what we want. What we want is to render the added element. The other four elements will not be re rendered without making any changes
In the case of using a unique {key}, the corresponding positional relationship of each element is} key. Let's take a look at the case of using a unique} key} value
In this way, as shown in the figure, {li3} and} li4} will not be re rendered, because the element content has not changed and the corresponding positional relationship has not changed.
This is why v-for must write a key, and it is not recommended to use the index of the array as the key in development
4. Use v-show to reuse DOM
v-show: is the rendering component, and then change the display of the component to block or none
Therefore, for scenarios where conditions can be changed frequently, use v-show to save performance. In particular, the more complex the DOM structure, the greater the benefit
However, it also has disadvantages, that is, at the beginning of v-show, all components inside the branch will be rendered, and the corresponding life cycle hook functions will be executed, while v-if will only load the components that judge the condition hit, so appropriate instructions need to be used according to different scenarios
For example, the following example uses "v-show" to reuse DOM, which is better than "v-if/v-else"
<template> <div> <div v-show="status" class="on"> <my-components /> </div> <section v-show="!status" class="off"> <my-components > </section> </div> </template>
The principle is to use v-if to trigger diff update when the condition changes. If it is found that the old and new vnodes are inconsistent, the whole old vnodes will be removed, and then a new vnodes will be re created. Then a new {my components} component will go through the processes of component initialization, render, patch}. When the condition changes, the old and new vnodes} are consistent, A series of processes such as removal and creation will not be performed
5. Functional components for stateless components
For some pure display components without responsive data, state management and life cycle hook function, we can set them as functional components to improve rendering performance. Because they will be treated as a function, the overhead is very low
The principle is that during the "patch" process, there will be no recursive sub component initialization process for the virtual DOM generated by the "render" of functional components, so the rendering overhead will be much lower
It can accept {props, but because it will not create an instance, it cannot use} This internally XX , get component properties, written as follows
<template functional> <div> <div class="content">{{ value }}</div> </div> </template> <script> export default { props: ['value'] } </script> //Or Vue.component('my-component', { functional: true, //Indicates that the component is a functional component props: { ... }, //Optional //The second parameter is context, without {this render: function (createElement, context) { // ... } })
6. Sub component segmentation
Let's start with an example
<template> <div :style="{ opacity: number / 100 }"> <div>{{ someThing() }}</div> </div> </template> <script> export default { props:['number'], methods: { someThing () { /* Time consuming task*/ } } } </script>
In the above code, every time the "number" passed by the parent component changes, it will be re rendered and the time-consuming task of "someThing" will be re executed
Therefore, for optimization, one is to use the calculation attribute, because the calculation attribute has the characteristic of caching the calculation results
The second is to split Vue into sub components, because the update of Vue is component granularity. Although the second data change will lead to the re rendering of the parent component, the sub component will not be re rendered because there is no internal change and the time-consuming tasks will not be re executed. Therefore, the performance is better. The optimization code is as follows
<template> <div> <my-child /> </div> </template> <script> export default { components: { MyChild: { methods: { someThing () { /* Time consuming task*/ } }, render (h) { return h('div', this.someThing()) } } } } </script>
7. Variable localization
Simply put, save the variables that will be referenced many times, because each time you access {this When XX , because it is a responsive object, the , getter will be triggered every time, and then the relevant code of dependency collection will be executed. The more variables are used, the worse the performance will naturally be
In terms of requirements, it is enough to perform dependency collection once for a variable in a function, but many people habitually write a lot of this in the project XX, ignoring {this What XX does behind the scenes will lead to performance problems
For example, the following example
<template> <div :style="{ opacity: number / 100 }"> {{ result }}</div> </template> <script> import { someThing } from '@/utils' export default { props: ['number'], computed: { base () { return 100 }, result () { let base = this.base, number = this.number // Save it for (let i = 0; i < 1000; i++) { number += someThing(base) //Avoid frequent references this.xx } return number } } } </script>
8. Third party plug-ins are introduced on demand
For example, third-party component libraries such as element UI can be introduced on demand to avoid too large volume. Especially when the project is small, it is not necessary to introduce component libraries completely
// main.js import Element3 from "plugins/element3"; Vue.use(Element3) // element3.js //Complete introduction import element3 from "element3"; import "element3/lib/theme-chalk/index.css"; //On demand import // import "element3/lib/theme-chalk/button.css"; // ... // import { // ElButton, // ElRow, // ElCol, // ElMain, // ..... // } from "element3"; export default function (app) { //Complete introduction app.use(element3) //On demand import // app.use(ElButton); }
9. Route lazy loading
We know that Vue is a single page application, so if it is not loaded lazily, it will lead to too much content to be loaded when entering the home page. If it takes too long, there will be a white screen for a long time, which is not conducive to the user experience and SEO is not friendly
Therefore, you can use lazy loading to divide the pages and load the corresponding pages when necessary, so as to share the loading pressure of the home page and reduce the loading time of the home page
Not loaded with route:
import Home from '@/components/Home' const router = new VueRouter({ routes: [ { path: '/home', component: Home } ] })
Loaded with routing lazy:
const router = new VueRouter({ routes: [ { path: '/home', component: () => import('@/components/Home') }, { path: '/login', component: require('@/components/Home').default } ] })
Only when you enter this route will you go to the corresponding {component, and then run} import} to compile and load components, which can be understood as the} resolve} mechanism of} Promise}
-
import: Es6 syntax specification, compile time call, is a deconstruction process, does not support variable functions, etc
-
require: AMD specification, runtime call, assignment process, variable calculation function, etc
10. Keep alive cache page
For example, when you enter the next step on the form input page and return to the previous step to the form page, you should keep the contents of the form input, such as the scene of jumping back and forth in list page > details page > list page, etc
We can cache components through the built-in component < keep alive > < / keep alive >, and do not uninstall them during component switching, so that when we return again, we can quickly render from the cache instead of re render, so as to save performance
Just wrap the components you want to cache
<template> <div id="app"> <keep-alive> <router-view/> </keep-alive> </div> </template>
-
You can also use include/exclude to cache / not cache the specified components
-
The current component state can be obtained through two life cycles: activated/deactivated
11. Destruction of the incident
When a Vue component is destroyed, it will automatically unbind all its instructions and event listeners, but only the events of the component itself
For timers, addEventListener registered listeners, etc., you need to manually destroy or unbind them in the life cycle hook of component destruction to avoid memory leakage
<script> export default { created() { this.timer = setInterval(this.refresh, 2000) addEventListener('touchmove', this.touchmove, false) }, beforeDestroy() { clearInterval(this.timer) this.timer = null removeEventListener('touchmove', this.touchmove, false) } } </script>
12. Picture loading
Lazy loading of pictures means that for pages with many pictures, in order to improve the page loading speed, only the pictures in the visual area are loaded, and those outside the visual area are loaded after scrolling to the visual area
This function comes with some UI frameworks. What if not?
Recommend a third-party plug-in Vue lazload
npm i vue-lazyload -S // main.js import VueLazyload from 'vue-lazyload' Vue.use(VueLazyload) //Then you can load the image in the page using {v-lazy} <img v-lazy="/static/images/1.png">
Or build your own wheel and manually encapsulate a user-defined instruction. A version compatible with each browser is encapsulated here. The main purpose is to judge whether the browser supports the IntersectionObserver API. If it supports it, use it to realize lazy loading, and if not, use the method of listening to scroll events + throttling
const LazyLoad = { //install method install(Vue, options) { const defaultSrc = options.default Vue.directive('lazy', { bind(el, binding) { LazyLoad.init(el, binding.value, defaultSrc) }, inserted(el) { if (IntersectionObserver) { LazyLoad.observe(el) } else { LazyLoad.listenerScroll(el) } }, }) }, //Initialization init(el, val, def) { el.setAttribute('data-src', val) el.setAttribute('src', def) }, //Monitoring el with IntersectionObserver observe(el) { var io = new IntersectionObserver((entries) => { const realSrc = el.dataset.src if (entries[0].isIntersecting) { if (realSrc) { el.src = realSrc el.removeAttribute('data-src') } } }) io.observe(el) }, //Listen for scroll events listenerScroll(el) { const handler = LazyLoad.throttle(LazyLoad.load, 300) LazyLoad.load(el) window.addEventListener('scroll', () => { handler(el) }) }, //Load real pictures load(el) { const windowHeight = document.documentElement.clientHeight const elTop = el.getBoundingClientRect().top const elBtm = el.getBoundingClientRect().bottom const realSrc = el.dataset.src if (elTop - windowHeight < 0 && elBtm > 0) { if (realSrc) { el.src = realSrc el.removeAttribute('data-src') } } }, //Throttling throttle(fn, delay) { let timer let prevTime return function (...args) { const currTime = Date.now() const context = this if (!prevTime) prevTime = currTime clearTimeout(timer) if (currTime - prevTime > delay) { prevTime = currTime fn.apply(context, args) clearTimeout(timer) return } timer = setTimeout(function () { prevTime = Date.now() timer = null fn.apply(context, args) }, delay) } }, } export default LazyLoad
The usage is as follows: use {v-LazyLoad} instead of} src
<img v-LazyLoad="xxx.jpg" />