Performance optimization tips in 12 Vue development

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" />

13. SSR

Keywords: Javascript Vue.js Optimize

Added by kelas on Wed, 05 Jan 2022 11:36:54 +0200