preface

Vue has improved our development efficiency and page performance, but in some cases Vue page performance is significantly lower than using jQuery directly.

I’ve written before about virtual lists handling a lot of data, but there are some third-party UI libraries that are used in projects or where virtual lists are not applicable.

Recently we have looked at the rendering mechanism of Vue in detail. Here is how we developers can avoid some performance bottlenecks in Vue pages.

start

Let’s define a component and optimize it step by step.

// Template <div class="container"> <div style="margin-bottom: 20px"> <button @click='search'> </button> </div> <! <a-table :data-source="tableData" :columns="columns" :row-key="record => record. Id" :pagination="false" > <template v-for="key in keys" :prop="key" :laba="key" #[key]="text, row" > {{ row[key] }} </template> </a-table> </div> // js export default { data: function() { return { tableData: [], keys: [], columns: [], } }, methods: { search() { const { data, keys } = this.getData() this.keys = keys this.columns = keys.map(key => ({ title: key, key: key, dataIndex: key, scopedSlots: { customRender: GetData () {const arr = [] let I = 1000 while (I --) {const item = {id: i, a0: 1, a1: 1, a2: 1, a3: 1, a4: 1, a5: 1, a6: 1, a7: 1, a8: 1, a9: 1} const data = { id: i, b0: item, b1: item, b2: item, b3: item, b4: item, b5: item, b6: item, b7: item, b8: item, b9: item } arr.push(data) } return { data: arr, keys: Object.keys(arr[0]) } } } }Copy the code

How to render large amounts of data

At this point we’ve defined a component that renders a lot of data at the click of a button.

performance

The time to create the fake data in the query is negligible, leaving Vue with a total of 2.3 seconds to add a response to each piece of data and 1.26 seconds to render, which takes about 3.6 seconds from the time we click the button to the time we see the data.

To optimize the

The continuous js execution time above is too long, which will lead to the page cannot interact for a long time. Besides, the rendering time is too slow, and the feedback time of a user interaction is too long. We optimize these two points.

{// search() {// omit code... // this.tableData = data this.setData(data)}, setData(data) {if (! Data.length) return requestAnimationFrame(Async () => {const num = 100 this.tableData.push(... data.slice(0, num)) this.setData(data.slice(num)) }) }, }Copy the code

The effect

Can see just task is divided into 10 portions, each portion of the gap are rendering, this time we can reduce the time of continuous operation of js, and speed up the rendering time, using the extended total run time for the rendering time, users can quickly get feedback, and will not run long time because of js to interact with the page.

Effect of detailed

When we narrowed the area down, we found that it took users 3694-3160 = 500(ms) to get feedback, which is 6 times faster than the previous 3.6s!

conclusion

In the Vue project, we tried to avoid rendering large amounts of data at once, and batch rendering was better.

The importance of componentization

We will continue to encapsulate components during the development of the Vue project, which will not only facilitate maintenance, but also greatly improve performance. The following code will continue to talk about the impact of componentization on performance.

We define num: 0 in data and place a button next to the query button.

<div style="margin-bottom: 20px"> <button @click='search'> </button> <! -- increase the button - > < button @ click = 'num++' style = "margin - left: 20 px" > {{num}} < / button > < / div >Copy the code

performance

When we have a lot of data in the page, we click on the newly created button and we find that the page is very sluggish.

I clicked twice in total, each of which executed about 1.7s of JS, and when I clicked on the Run JS task, it was the Vue method that updated the current component that was taking up time.

Since the response of Vue after Vue2. X is updated in component granularity, as long as the data used in the current component is modified (num is what we modify), the component will be updated in whole. In 1.7s, we spend a lot of time comparing diff algorithms on table data.

To optimize the

What we need to do now is to separate the table into a component.

// MyTable.vue // template <div> <! <a-table :data-source="tableData" :columns="columns" :row-key="record => record. Id" :pagination="false" > <template v-for="key in keys" :prop="key" :laba="key" #[key]="text, </template> </a-table> </div> // js export default {// need data props: ['keys', 'tableData', 'columns'] }Copy the code
// template <! <MyTable :keys="keys" :tableData="tableData" :columns="columns" /> // import MyTable from './MyTable.vue'Copy the code

The effect

I still click twice and you can see that the JS task, which is not nearly as long as it was before, now takes 6ms to run js, which is 573 times faster (1719/3) than before the component was wrapped.

why

The reason why encapsulated components perform so well is because of Vue’s update mechanism.

When Vue is updated, patch method will be called to compare virtual DOM. PatchVnode will be called in path, and there will be a hook in each component generation with a prepatch method. This method is entered when comparing the MyTable component.

/ / components of hook statement const componentVNodeHooks: {init (vnode: any, hydrating, Boolean) : Boolean | null; prepatch(oldVnode: any, vnode: any): void; insert(vnode: any): void; destroy(vnode: any): void; }Copy the code
Function patchVnode (oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly) { Let I const data = vnode.data if (isDef(data) &&isdef (I = data.hook) &&isdef (I = i.patch)) Prepatch method I (oldVnode, vnode)} }Copy the code

This prepatch calls the updateChildComponent method, which took too long before we wrapped the component.

prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
    const options = vnode.componentOptions
    const child = vnode.componentInstance = oldVnode.componentInstance
    updateChildComponent(
      child,
      options.propsData, // updated props
      options.listeners, // updated listeners
      vnode, // new parent vnode
      options.children // new children
    )
}
Copy the code

If we look at this method, we can see that there is a line for update props. Let’s not use the doll function again, it’s too deep.

When the sub component is initialized, it changes its props to reactive. In the following code, it assigns values to the props. If the value of the props used by the sub component changes, it triggers an update of the sub component. But at this point, because the values inside the props have not changed, the child component does not trigger the update.

export function updateChildComponent ( vm: Component, propsData: ? Object, listeners: ? Object, parentVnode: MountedComponentVNode, renderChildren: ? Array<VNode>) {// omit code... // update props if (propsData && vm.$options.props) { toggleObserving(false) const props = vm._props const propKeys = vm.$options._propKeys || [] for (let i = 0; i < propKeys.length; i++) { const key = propKeys[i] const propOptions: any = vm.$options.props // wtf flow? props[key] = validateProp(key, propOptions, propsData, Vm)} toggleObserving(true) // Keep a copy of raw propsData vm.$options.propsData = propsData} }Copy the code

conclusion

At this time, we should know the importance of componentization. It is not only good for our maintenance, but also can greatly improve the performance of our project. Therefore, we should try to componentize in the development process.

Impact of slots on components

Slots are also something we use a lot. We’ve seen how componentization improves performance, but we’re going to pick up where we left off, and we’re going to talk about how slots affect our componentization performance.

We just wrote a little bit of text in the previous component, and the default slot in the child component was optional.

// template <MyTable :keys="keys" :tableData="tableData" :columns="columns" >Copy the code

performance

At this time, I went to click the number button again and found it suddenly stuck.

why

The result of needsForceUpdate is true. At the bottom, the MyTable component is forced to update. Any static slot child of the parent may have changed during the parent update. The dynamic scope slot may also have changed. In this case, updates need to be enforced to ensure correctness.)

export function updateChildComponent ( vm: Component, propsData: ? Object, listeners: ? Object, parentVnode: MountedComponentVNode, renderChildren: ? Array<VNode>) {// omit code... // Any static slot children from the parent may have changed during parent's update. Dynamic scoped slots may also have changed. In such cases, a forced update is necessary to ensure correctness. const needsForceUpdate = !! ( renderChildren || // has new static slots vm.$options._renderChildren || // has old static slots hasDynamicScopedSlot ) // resolve slots + force update if has children if (needsForceUpdate) { vm.$slots = resolveSlots(renderChildren, Parentvnode.context) vm.$forceUpdate()} // omit code... }Copy the code

The dynamic slot

In the case of static slots, the child component forces updates to ensure correctness. In the case of dynamic slots, the child component forces updates to ensure correctness.

// template <MyTable :keys="keys" :tableData=" columns" :columns="columns" > <template #[myName]> </template> </MyTable>Copy the code

performance

This time continue to click the digital button, found that it is still card, and the effect of the static slot is consistent.

conclusion

Static slots are the same as dynamic slots. To ensure correctness, they are forced to update. You can also take a look at the original code that did not remove the component.

Scope slot

For scoped slots, which are special and need to be described separately, let’s first look at the compilation results of different slots.

Compile the contrast

As you can see, the static slot content is generated in the parent component, while the scope slot content is encapsulated in a fn function. The difference is that the scope slot content is not generated in the parent component, but waits for the child component to call the FN function and generate the content in the child component. This means that child components are notified of updates only when the data used by slots in the scope changes.

Static slot:

// template <MyTable :keys="keys" :tableData="tableData" :columns="columns" render() { with(this) { return _c('MyTable', { attrs: { "keys": keys, "tableData": tableData, "columns": Columns}, [_v("\n ")])}}Copy the code

Scope slot:

// template <MyTable :keys="keys" :tableData=" columns" :columns="columns" > <template #default </template> </MyTable> function render() {with(this) {return _c('MyTable', {attrs: {"keys": keys, "tableData": tableData, "columns": columns }, scopedSlots: _u([{ key: "default", fn: The function () {return [_v (" \ n I just a simple static text \ n ")]}, proxy: true}])}}}Copy the code

performance

This time we go to the dynamic slot into the scope slot, try the effect.

Template <MyTable :keys="keys" :tableData=" columns" :columns="columns" > <template #default </template> </MyTable>Copy the code

Go click the button!

The effect

I clicked it twice and found that the total time of js running was only 4 ms.

The last

Having said so much, we should have a certain understanding of Vue componentization, if there are mistakes in the article, welcome to correct!!