|
1 | 1 | ## 你了解哪些Vue性能优化方法?
|
2 | 2 |
|
3 |
| -### 答题思路:根据题目描述,这里主要探讨Vue代码层面的优化 |
| 3 | +### 分析 |
4 | 4 |
|
| 5 | +这是一道综合实践题目,写过一定数量的代码之后小伙伴们自然会开始关注一些优化方法,答得越多肯定实践经验也越丰富,是很好的题目。 |
| 6 | + |
| 7 | +### 答题思路: |
| 8 | + |
| 9 | +根据题目描述,这里主要探讨Vue代码层面的优化 |
5 | 10 |
|
6 | 11 | ### 回答范例
|
7 |
| -- 路由懒加载 |
| 12 | + |
| 13 | +- 我这里主要从Vue代码编写层面说一些优化手段,例如:代码分割、服务端渲染、组件缓存、长列表优化等 |
| 14 | + |
| 15 | +- 最常见的路由懒加载:有效拆分App尺寸,访问时才异步加载 |
8 | 16 |
|
9 | 17 | ```js
|
10 |
| - const router = new VueRouter({ |
| 18 | + const router = createRouter({ |
11 | 19 | routes: [
|
| 20 | + // 借助webpack的import()实现异步组件 |
12 | 21 | { path: '/foo', component: () => import('./Foo.vue') }
|
13 | 22 | ]
|
14 | 23 | })
|
15 | 24 | ```
|
16 | 25 |
|
17 | 26 |
|
18 | 27 |
|
19 |
| -- keep-alive缓存页面 |
| 28 | +- `keep-alive`缓存页面:避免重复创建组件实例,且能保留缓存组件状态 |
20 | 29 |
|
21 | 30 | ```vue
|
22 |
| - <template> |
23 |
| - <div id="app"> |
24 |
| - <keep-alive> |
25 |
| - <router-view/> |
26 |
| - </keep-alive> |
27 |
| - </div> |
28 |
| - </template> |
| 31 | + <router-view v-slot="{ Component }"> |
| 32 | + <keep-alive> |
| 33 | + <component :is="Component"></component> |
| 34 | + </keep-alive> |
| 35 | + </router-view> |
29 | 36 | ```
|
30 | 37 |
|
31 | 38 |
|
32 | 39 |
|
33 |
| -- 使用v-show复用DOM |
| 40 | +- 使用`v-show`复用DOM:避免重复创建组件 |
34 | 41 |
|
35 | 42 | ```vue
|
36 | 43 | <template>
|
37 | 44 | <div class="cell">
|
38 |
| - <!--这种情况用v-show复用DOM,比v-if效果好--> |
| 45 | + <!-- 这种情况用v-show复用DOM,比v-if效果好 --> |
39 | 46 | <div v-show="value" class="on">
|
40 | 47 | <Heavy :n="10000"/>
|
41 | 48 | </div>
|
|
48 | 55 |
|
49 | 56 |
|
50 | 57 |
|
51 |
| -- v-for 遍历避免同时使用 v-if |
| 58 | +- `v-for` 遍历避免同时使用 `v-if`:实际上在Vue3中已经是个错误写法 |
52 | 59 |
|
53 | 60 | ```vue
|
54 | 61 | <template>
|
55 | 62 | <ul>
|
56 | 63 | <li
|
57 | 64 | v-for="user in activeUsers"
|
| 65 | + <!-- 避免同时使用,vue3中会报错 --> |
| 66 | + <!-- v-if="user.isActive" --> |
58 | 67 | :key="user.id">
|
59 | 68 | {{ user.name }}
|
60 | 69 | </li>
|
61 | 70 | </ul>
|
62 | 71 | </template>
|
63 | 72 | <script>
|
64 |
| - export default { |
65 |
| - computed: { |
66 |
| - activeUsers: function () { |
67 |
| - return this.users.filter(function (user) { |
68 |
| - return user.isActive |
69 |
| - }) |
70 |
| - } |
71 |
| - } |
| 73 | + export default { |
| 74 | + computed: { |
| 75 | + activeUsers: function () { |
| 76 | + return this.users.filter(user => user.isActive) |
| 77 | + } |
72 | 78 | }
|
| 79 | + } |
73 | 80 | </script>
|
74 | 81 | ```
|
75 | 82 |
|
76 | 83 |
|
77 | 84 |
|
78 |
| -- 长列表性能优化 |
| 85 | +- v-once和v-memo:不再变化的数据使用`v-once` |
79 | 86 |
|
80 |
| - - 如果列表是纯粹的数据展示,不会有任何改变,就不需要做响应化 |
| 87 | + ```vue |
| 88 | + <!-- single element --> |
| 89 | + <span v-once>This will never change: {{msg}}</span> |
| 90 | + <!-- the element have children --> |
| 91 | + <div v-once> |
| 92 | + <h1>comment</h1> |
| 93 | + <p>{{msg}}</p> |
| 94 | + </div> |
| 95 | + <!-- component --> |
| 96 | + <my-component v-once :comment="msg"></my-component> |
| 97 | + <!-- `v-for` directive --> |
| 98 | + <ul> |
| 99 | + <li v-for="i in list" v-once>{{i}}</li> |
| 100 | + </ul> |
| 101 | + ``` |
81 | 102 |
|
82 |
| - ```js |
83 |
| - export default { |
84 |
| - data: () => ({ |
85 |
| - users: [] |
86 |
| - }), |
87 |
| - async created() { |
88 |
| - const users = await axios.get("/api/users"); |
89 |
| - this.users = Object.freeze(users); |
90 |
| - } |
91 |
| - }; |
92 |
| - ``` |
93 |
| - |
94 |
| - |
95 |
| - |
96 |
| - - 如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域的内容 |
97 |
| - |
98 |
| - ```html |
99 |
| - <recycle-scroller |
100 |
| - class="items" |
101 |
| - :items="items" |
102 |
| - :item-size="24" |
103 |
| - > |
104 |
| - <template v-slot="{ item }"> |
105 |
| - <FetchItemView |
106 |
| - :item="item" |
107 |
| - @vote="voteItem(item)" |
108 |
| - /> |
109 |
| - </template> |
110 |
| - </recycle-scroller> |
111 |
| - ``` |
112 |
| - |
113 |
| - > 参考[vue-virtual-scroller](https://github.com/Akryum/vue-virtual-scroller)、[vue-virtual-scroll-list](https://github.com/tangbc/vue-virtual-scroll-list) |
| 103 | + 按条件跳过更新时使用`v-momo`:下面这个列表只会更新选中状态变化项 |
114 | 104 |
|
115 |
| - |
| 105 | + ```vue |
| 106 | + <div v-for="item in list" :key="item.id" v-memo="[item.id === selected]"> |
| 107 | + <p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p> |
| 108 | + <p>...more child nodes</p> |
| 109 | + </div> |
| 110 | + ``` |
116 | 111 |
|
117 |
| -- 事件的销毁 |
| 112 | + > https://vuejs.org/api/built-in-directives.html#v-memo |
| 113 | +
|
| 114 | + |
118 | 115 |
|
119 |
| - Vue 组件销毁时,会自动解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。 |
| 116 | +- 长列表性能优化:如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域的内容 |
120 | 117 |
|
| 118 | + ```html |
| 119 | + <recycle-scroller |
| 120 | + class="items" |
| 121 | + :items="items" |
| 122 | + :item-size="24" |
| 123 | + > |
| 124 | + <template v-slot="{ item }"> |
| 125 | + <FetchItemView |
| 126 | + :item="item" |
| 127 | + @vote="voteItem(item)" |
| 128 | + /> |
| 129 | + </template> |
| 130 | + </recycle-scroller> |
121 | 131 | ```
|
122 |
| - created() { |
123 |
| - this.timer = setInterval(this.refresh, 2000) |
124 |
| - }, |
125 |
| - beforeDestroy() { |
126 |
| - clearInterval(this.timer) |
| 132 | + |
| 133 | + > 一些开源库: |
| 134 | + > |
| 135 | + > - [vue-virtual-scroller](https://github.com/Akryum/vue-virtual-scroller) |
| 136 | + > - [vue-virtual-scroll-grid](https://github.com/rocwang/vue-virtual-scroll-grid) |
| 137 | +
|
| 138 | + |
| 139 | + |
| 140 | +- 事件的销毁:Vue 组件销毁时,会自动解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。 |
| 141 | + |
| 142 | + ```js |
| 143 | + export default { |
| 144 | + created() { |
| 145 | + this.timer = setInterval(this.refresh, 2000) |
| 146 | + }, |
| 147 | + beforeUnmount() { |
| 148 | + clearInterval(this.timer) |
| 149 | + } |
127 | 150 | }
|
128 | 151 | ```
|
129 | 152 |
|
|
143 | 166 |
|
144 | 167 | - 第三方插件按需引入
|
145 | 168 |
|
146 |
| - 像element-ui这样的第三方组件库可以按需引入避免体积太大。 |
| 169 | + 像`element-plus`这样的第三方组件库可以按需引入避免体积太大。 |
147 | 170 |
|
148 | 171 | ```js
|
149 |
| - import Vue from 'vue'; |
150 |
| - import { Button, Select } from 'element-ui'; |
| 172 | + import { createApp } from 'vue'; |
| 173 | + import { Button, Select } from 'element-plus'; |
151 | 174 |
|
152 |
| - Vue.use(Button) |
153 |
| - Vue.use(Select) |
| 175 | + const app = createApp() |
| 176 | + app.use(Button) |
| 177 | + app.use(Select) |
154 | 178 | ```
|
155 | 179 |
|
156 | 180 |
|
157 | 181 |
|
158 |
| -- 无状态的组件标记为函数式组件 |
159 |
| - |
160 |
| - ```vue |
161 |
| - <template functional> |
162 |
| - <div class="cell"> |
163 |
| - <div v-if="props.value" class="on"></div> |
164 |
| - <section v-else class="off"></section> |
165 |
| - </div> |
166 |
| - </template> |
167 |
| - |
168 |
| - <script> |
169 |
| - export default { |
170 |
| - props: ['value'] |
171 |
| - } |
172 |
| - </script> |
173 |
| - ``` |
174 |
| - |
175 |
| - |
176 |
| - |
177 |
| -- 子组件分割 |
| 182 | +- 子组件分割策略:较重的状态组件适合拆分 |
178 | 183 |
|
179 | 184 | ```vue
|
180 | 185 | <template>
|
|
199 | 204 | </script>
|
200 | 205 | ```
|
201 | 206 |
|
202 |
| - |
203 |
| - |
204 |
| -- 变量本地化 |
| 207 | + 但同时也不宜过度拆分组件,尤其是为了所谓组件抽象将一些不需要渲染的组件特意抽出来,组件实例消耗远大于纯dom节点。参考:https://vuejs.org/guide/best-practices/performance.html#avoid-unnecessary-component-abstractions |
205 | 208 |
|
206 |
| - ```vue |
207 |
| - <template> |
208 |
| - <div :style="{ opacity: start / 300 }"> |
209 |
| - {{ result }} |
210 |
| - </div> |
211 |
| - </template> |
212 | 209 |
|
213 |
| - <script> |
214 |
| - import { heavy } from '@/utils' |
215 |
| - |
216 |
| - export default { |
217 |
| - props: ['start'], |
218 |
| - computed: { |
219 |
| - base () { return 42 }, |
220 |
| - result () { |
221 |
| - const base = this.base // 不要频繁引用this.base |
222 |
| - let result = this.start |
223 |
| - for (let i = 0; i < 1000; i++) { |
224 |
| - result += heavy(base) |
225 |
| - } |
226 |
| - return result |
227 |
| - } |
228 |
| - } |
229 |
| - } |
230 |
| - </script> |
231 |
| - ``` |
232 | 210 |
|
233 |
| - |
| 211 | +- 服务端渲染/静态网站生成:SSR/SSG |
| 212 | + |
| 213 | + 如果SPA应用有首屏渲染慢的问题,可以考虑SSR、SSG方案优化。参考[SSR Guide](https://vuejs.org/guide/scaling-up/ssr.html) |
234 | 214 |
|
235 |
| -- SSR |
0 commit comments