Performance optimization, is each developer has to deal with a problem, especially now pay more and more attention to experience, and an increasingly competitive environment, to our developers, only completed iterations, the function is not enough, the most important thing is to put the products, let more people like to use, allows users to use more bright, Isn’t it also a reflection of our developer’s value and ability?
Focusing on performance issues and improving the product experience is far more valuable than fixing a few minor bugs
This article has documented some of my tips for daily development of Vue projects. Without further ado, let’s get started
1. Long list performance optimization
1. Do not respond
Such as membership list, commodity list and so on, are only pure data display, without any dynamic change of the scene, there is no need to do responsive processing to the data, which can greatly improve the rendering speed
For example, using object.freeze () to freeze an Object, MDN says that the Object frozen by this method cannot be modified. That is, new attributes cannot be added to the object, existing attributes cannot be deleted, the enumerability, configurability, writability of existing attributes cannot be modified, the value of existing attributes cannot be modified, and the prototype of the object cannot be modified
export default { data: () => ({ userList: [] }), async created() { const users = await axios.get("/api/users"); this.userList = Object.freeze(users); }};Copy the code
Vue2 reactive source address: SRC/core/observer/index. The js – 144 line is as follows
export function defineReactive (...) { const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } ... }Copy the code
The 64x signals, which is configured with a different information system, are configured without any response
If the 64x is set to false, the property cannot be modified, and the frozen object works without any additional control system
Vue3 adds a responsive flag to mark the target object type
2. Virtual scroll
If you have a long list of big data, creating too many DOM’s at once would be very difficult to render all at once. In this case, you can use virtual scrolling to render only a small part of the content (including the visual area), and then constantly replace the content of the visual area as you scroll to simulate the scrolling effect
<recycle-scroller
class="items"
:items="items"
:item-size="24"
>
<template v-slot="{ item }">
<FetchItemView
:item="item"
@vote="voteItem(item)"
/>
</template>
</recycle-scroller>
Copy the code
See vue-virtual-scroller and vue-virtual-scroll list
The principle is to listen for scroll events, dynamically update the DOM that needs to be displayed, and calculate the displacement in the view. This also means that the scrolling process needs to be calculated in real time, so there is a certain cost, so if the data volume is not very large, use ordinary scroll
2. V-for traversal Avoid v-if at the same time
Why avoid using both V-for and V-if
In Vue2, V-for has a higher priority, so the compilation process will traverse all the list elements to generate the virtual DOM, and then use V-if to judge the qualified virtual DOM, which will cause a waste of performance, because we hope that the unqualified virtual DOM will not be generated
V-if has a higher priority in Vue3, which means that when the criteria are attributes in the list traversed by V-FOR, v-IF can’t get them
So in some scenarios that need to be used at the same time, you can filter the list by calculating attributes, as follows
<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>
Copy the code
3. The list uses a unique key
For example, if we have a list and we need to insert an element in the middle, what happens if we don’t use the key or if we use index as the key? Look at a map
Li1 and li2 will not be re-rendered, this is not controversial. Li3, li4, and li5 are all re-rendered
When the key or index of the list is not used as the key, the corresponding position relation of each element is index. The result in the figure above directly leads to the change of the corresponding position relation between the element we inserted and all the following elements, so all of them will be updated in the patch process. Re-render.
This is not what we want, what we want is to render the one element that was added, and not re-render the other four elements without making any changes
In the case of a unique key, the position of each element is the key, so let’s look at the case of a unique key
Li3 and LI4 in the figure will not be re-rendered because the element content has not changed and the corresponding position relationship has not changed.
This is why v-for must write the key, and it is not recommended to use the index of the array as the key
4. Use V-show to reuse DOM
V-show: renders the component and then changes the display of the component to block or None v-if: renders or does not render the component
Therefore, for scenarios where conditions can be changed frequently, v-show is used to save performance, especially if the DOM structure is more complex
The disadvantage of v-show is that when v-show starts, all the components inside the branch are rendered and the corresponding lifecycle hook functions are executed, whereas V-IF only loads the components that are judged to have hit the condition, so you need to use appropriate instructions for different scenarios
For example, using v-show to reuse DOM is better than using 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>Copy the code
The principle is to use V-if when conditions change, trigger diff update, find the old vnode is inconsistent with the old vnode, remove the whole old Vnode, create a new Vnode, and then create a new my-Components component, and then undergo the component initialization, render, When conditions change, the old and new VNodes are the same, so a series of processes such as removal and creation will not be performed
5. Stateless components use functional components
For purely presentable components with no responsive data, no state management, and no lifecycle hook functions, we can set up functional components to improve rendering performance because it is treated as a function, so the overhead is low
The principle is that there is no recursive sub-component initialization process for the virtual DOM generated by the render of functional components in the patch process, so the rendering cost is much lower
It accepts props, but because it does not create instances, it cannot internally get component properties using this.xx, as follows
<template functional> <div> <div class="content">{{ value }}</div> </div> </template> <script> export default { props: ['value']} </script> // Vue.component('my-component', {functional: true, // to say that this component is a functional component, props: {... }, // This render: function (createElement, context) {//... }})Copy the code
6. Sub-component segmentation
Let’s look at an example
<template> <div :style="{ opacity: number / 100 }"> <div>{{ someThing() }}</div> </div> </template> <script> export default { props:['number'], methods: {someThing () {/* Time-consuming task */}}} </script>Copy the code
In code like the one above, every time the number passed by the parent changes, it rerenders and re-executes the time-consuming task of someThing
So one way to optimize is to use computed properties, because computed properties themselves have the property of caching computed results
The second is to split into sub-components, because the update of Vue is component granularity, although the first data change will lead to the re-rendering of the parent component, but the sub-component will not re-render, because there is no change in its internal, time-consuming tasks will not be re-executed, so the performance is better, the optimized code is as follows
<template> <div> <my-child /> </div> </template> <script> export default { components: { MyChild: { methods: Render (h) {return h('div', this.something ())}}}} </script>Copy the code
7. Localize variables
Every time you access this.xx, the getter will be triggered, and the dependent collection code will be executed. If you use the variable more times, the performance will be worse
The need to perform dependency collection once for a variable in a function is sufficient, but many people are used to writing a lot of this.xx in their projects, ignoring what this. Xx does behind it, which can lead to performance problems
Here’s an 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 i < 1000; I++) {number += someThing(base) // avoid frequent references to this.xx} return number}}} </script>Copy the code
8. Introduce third-party plug-ins on demand
Third-party component libraries such as Element-UI can be introduced on demand to avoid being too bulky, especially if the project is small, and there is no need to introduce the entire component library
// main.js import Element3 from "plugins/element3"; Vue. Use (Element3) // element3.js import Element3 from "Element3 "; import "element3/lib/theme-chalk/index.css"; // import "element3/lib/theme-chalk/button.css"; / /... // import { // ElButton, // ElRow, // ElCol, // ElMain, // ..... // } from "element3"; Export default function (app) {app.use(element3) // app.use(ElButton); }Copy the code
9. Lazy route loading
As we know, Vue is a single-page application, so if we do not use lazy loading, it will lead to too much content to load when entering the home page, and the time is too long, there will be a long blank screen, which is not conducive to user experience, SEO is not friendly
So you can use lazy loading to divide the page, load the corresponding page when necessary, to share the load pressure of the home page, reduce the load time of the home page
No lazy loading of routes:
import Home from '@/components/Home'
const router = new VueRouter({
routes: [
{ path: '/home', component: Home }
]
})
Copy the code
Using route lazy loading:
const router = new VueRouter({
routes: [
{ path: '/home', component: () =>
import('@/components/Home') },
{ path: '/login', component:
require('@/components/Home').default }
]
})
Copy the code
Enter the route with the corresponding Component, and then run import to compile and load the component, which can be interpreted as Promise’s resolve mechanism
import
: Es6 syntax specification, compile-time invocation, is a deconstruction procedure, does not support variable functions, etcrequire
: AMD specification, runtime call, is the assignment process, support variable calculation function, etc
More on front-end modularity can be found in my other article, a detailed summary of the front-end modularity specification
10. Keep-alive cache the page
For example, after entering the next step on the form input page, the input content of the form should be retained when returning to the form page, such as the list page > Details page > list page, and so on
We can all use the built-in
You only need to wrap the components you want to cache
<template>
<div id="app">
<keep-alive>
<router-view/>
</keep-alive>
</div>
</template>
Copy the code
- You can also use
include/exclude
To cache/not cache specified components - It can go through two life cycles
activated/deactivated
To get the current component state
11. Destruction of events
When a Vue component is destroyed, all its instructions and event listeners are automatically unbound, but only for the component’s own events
For timers, listeners registered with addEventListener, etc., you need to manually destroy or unbind them in the component destruction lifecycle hook to avoid memory leaks
<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>
Copy the code
12. Lazy loading of images
Image lazy loading means that for a page with many images, in order to improve the loading speed of the page, only the images in the visual area are loaded, and those outside the visual area are loaded after scrolling to the visual area
Some UI frameworks come with this functionality, but what if they don’t?
A third-party plugin called VUe-lazyLoad is recommended
NPM I vue-lazyload-s // main.js import VueLazyload from 'vue-lazyload' vue. use(VueLazyload) // Then v-lazy can be used in the page <img v-lazy="/static/images/1.png">Copy the code
In this case, a version compatible with all browsers is packaged, mainly to determine whether the browser does not support IntersectionObserver API, so it can be used to implement lazy loading; otherwise, it can be implemented by monitoring 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) } }, }) }, {el.setAttribute('data-src', val) el.setAttribute(' SRC ', def)}, // Use IntersectionObserver to monitor EL 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)}, // listenerScroll(el) {const handler = lazyload.load (lazyload.load, 300) LazyLoad.load(el) window.addEventListener('scroll', () => { handler(el) }) }, / / load the real images load (el) {const windowHeight = document. The 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') } } }, // Throttle 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 LazyLoadCopy the code
Use v-lazyload instead of SRC
<img v-LazyLoad="xxx.jpg" />
Copy the code