Below, I will share several optimization techniques in order of effectiveness. At the bottom, there will be a quick summary by category.
Component Splitting
I used to think that splitting sub-components was for abstraction, but practice has shown me that splitting sub-components is a way to improve performance (in specific cases).
In my actual work, I encountered a problem where there was a large table with multiple dialog boxes for adding new entries. When there was a lot of data, it became slow to fill in the new data in the dialog box.
The reason is that within a single component, modifying a value triggers the render function and diff for the entire component. But I know that I’m just filling in a number in the dialog box, and the form itself hasn’t changed. Why should I waste time checking unnecessary things?
To solve this problem, extracting the dialog box as a separate component became a very effective optimization method.
Because of Vue’s update mechanism, updates are done on a per-component basis. Updating a child component does not trigger an update in the parent component, unless the child component changes the parent component’s data.
Let’s take the example of the dialog box in element UI (open this link to directly run the example).
Create a page that contains two dialog boxes, one directly written in the page and the other abstracted as a component.
First, let’s look at the componentized dialog box, which is very simple:
The main page is as follows, containing an el-dialog
and an already abstracted my-dialog
:
In practice, it is known that when opening or closing the el-dialog
and modifying the data in the input box, the render function of the entire component will be triggered, while the my-dialog
will not trigger the update of the parent component regardless of whether it is opened, closed, or input. When the data in the component containing the dialog is small, there is indeed little difference, but when the data is large, there will be noticeable lag when inputting in the dialog box. (In short, the dialog box is a separate component, and internal updates do not affect the parent component)
Moreover, conversely, when the parent component is updated, the el-dialog
will be rendered without rendering the my-dialog
, which is truly a win-win situation. (In short, the child component without data changes will not be changed when the parent component is updated)
Even if this component is not reused, you can separate the methods used in the dialog box into a separate file, without mixing them with the methods of the main page. If a dialog box has a lot of logic, separating it into a separate file is definitely a good approach.
However, there are also disadvantages: data interaction is a bit inconvenient, but you can try to use $refs
, $parent
, Vuex, and other methods to solve it.
If you don’t like the solution I used above, this.$refs.myDialog.visible = true;
, you can also use the traditional $emit
and $on
methods.
For a more detailed explanation of the update granularity, you can refer to Vue and React: What are the differences in component update granularity.
Additionally, you can learn about optimizing child component updates in React in React Rendering Optimization: diff and shouldComponentUpdate.
P.S. There is also a hidden conclusion that if a component uses slots, the child components will be re-rendered along with the parent component.
Replacing Heavy Components
Open source libraries benefit everyone, but the more people they serve, the more redundant logic they contain. Normally, they do meet performance requirements, but if you really encounter a situation in your business where you need to loop through dozens or even hundreds of tables using v-for
, the efficiency impact caused by redundant logic should not be underestimated, and it will also consume more memory.
So, try creating your own libraries in the simplest way possible to meet your minimum requirements. Personally, I created a table component cpn-tbl and a lightweight validation plugin v-vld because I had too many tables and the table component in element UI was too complex.
When implementing components, you may need to be more familiar with the usage of the render function, so that you can manipulate the node structure more freely and concisely.
v-if
If you need the initial rendering to be as fast as possible, v-if
is definitely your good helper.
In addition, using asynchronous components in v-if
can delay the loading of some code.
With the help of webpack’s code splitting feature, you can easily achieve such asynchronous components. Of course, code splitting can also be used to delay the loading of large JavaScript files (such as heavy libraries).
P.S. Speaking of code splitting, this is an optimization method that must be understood regardless of which framework you use, especially with Webpack, where you can use simple syntax to split code and pursue the ultimate speed of the initial rendering.
On the other hand, using v-show
can cache components and increase the smoothness of toggling.
Chunk Rendering
This is an optimization particularly aimed at improving scrolling smoothness. As we all know, browser rendering and JavaScript execution are mutually exclusive. If a piece of code takes more than 1000/30 ms to run while the browser needs to render, it will appear to be less than 30 frames.
Related link: requestAnimationFrame
Caching AJAX Data
You can encapsulate it to be used just like a regular axios
GET request, directly replacing the original axios
object:
If you think the response.config.url + (params ? JSON.stringify(params) : '')
id is too long, you can add a hash()
to shorten it, but it will lose readability.
Freezing
For read-only data such as tables, there is no need to make the data “reactive”. Object.freeze()
is used for this scenario to prevent assignment to reactive data, saving the time of recursive “reactivity”. For large and deep objects, the time saved is quite significant.
Key for Loops
Purpose: to speed up the diff algorithm. However, remember not to use a simple index as the key. Also, when looping through a template, each child element needs a different key.
Speaking of which, I suddenly remembered that when using Element UI to render el-form-item in a loop, I wrote the key incorrectly, which caused infinite re-rendering. I debugged it for a long time 😂
Categorization Overview
Component-level optimization
- Split components to optimize update speed at the component level
- Be cautious with heavy components; create your own when necessary, the same applies to plugins
- Use functional components (lower priority)
Handling side effects of reactivity
- Utilize reactive anti-patterns
- Reduce the use of
this
in dependency collection (can optimize initial rendering speed)
Reducing rendering pressure
- Balance between v-show and v-if
- Render in chunks
Vue’s built-in caching
- keep-alive
- computed
Other optimizations
- Data caching
- Virtual scrolling
- Remove console.log