Skip to content

Commit 34db53d

Browse files
committed
update: 25-37题
1 parent dcfced3 commit 34db53d

File tree

14 files changed

+1101
-0
lines changed

14 files changed

+1101
-0
lines changed

README.md

+13
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,19 @@
3434
22. [说说从 template 到 render 处理过程](public/22-template-render/README.md)
3535
23. [如果让你从零写一个vue路由,说说你的思路](public/14-router/README.md)
3636
24. [Vue 3.0的设计目标是什么?做了哪些优化?](public/24-v3-design/README.md)
37+
25. [你了解哪些Vue性能优化方法?](public/25-performance/README.md)
38+
26. [Vue组件为什么只能有一个根元素?](public/26-one-root/README.md)
39+
27. [你有使用过vuex的module吗?](public/27-vuex-module/README.md)
40+
28. [怎么实现路由懒加载呢?](public/28-route-lazy-load/README.md)
41+
29. [ref和reactive异同](public/29-ref-reactive/README.md)
42+
30. [watch和watchEffect异同](public/30-watch-watchEffect/README.md)
43+
31. [SPA、SSR的区别是什么](public/31-SPA-SSR/README.md)
44+
32. [你写过自定义指令吗?使用场景有哪些?](public/32-directive/README.md)
45+
33. [说下\$attrs和$listeners的使用场景](public/33-%24attrs/README.md)
46+
34. [v-once的使用场景有哪些?](public/34-v-once/README.md)
47+
35. [什么是递归组件?举个例子说明下?](public/35-recursive-component/README.md)
48+
36. [异步组件是什么?使用场景有哪些?](public/36-async-component/README.md)
49+
37. [你是怎么处理vue项目中的错误的?](public/37-error-handle/README.md)
3750

3851
### 欢迎把你想听的题目以issue的方式提给我
3952
### 欢迎你加入村长的项目已pr形式提交题目和解答

public/25-performance/README.md

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

public/26-one-root/README.md

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
## 26-Vue组件为什么只能有一个根元素?
2+
3+
这题现在有些落伍,`vue3`已经不用一个根了。因此这题目很有说头!
4+
5+
---
6+
7+
### 体验一下
8+
9+
vue2直接报错,test-v2.html
10+
11+
```js
12+
new Vue({
13+
components: {
14+
comp: {
15+
template: `
16+
<div>root1</div>
17+
<div>root2</div>
18+
`
19+
}
20+
}
21+
}).$mount('#app')
22+
```
23+
24+
<img src="https://tva1.sinaimg.cn/large/e6c9d24egy1h2tpywiwwkj20ws0hm0va.jpg" style="zoom:33%;" />
25+
26+
---
27+
28+
vue3中没有问题,test-v3.html
29+
30+
```js
31+
Vue.createApp({
32+
components: {
33+
comp: {
34+
template: `
35+
<div>root1</div>
36+
<div>root2</div>
37+
`
38+
}
39+
}
40+
}).mount('#app')
41+
```
42+
43+
<img src="https://tva1.sinaimg.cn/large/e6c9d24egy1h2tq28fsfoj20xu0biq3x.jpg" style="zoom:33%;" />
44+
45+
---
46+
47+
### 回答思路
48+
49+
- 给一条自己的结论
50+
- 解释为什么会这样
51+
- `vue3`解决方法原理
52+
53+
---
54+
55+
### 范例
56+
57+
- `vue2`中组件确实只能有一个根,但`vue3`中组件已经可以多根节点了。
58+
- 之所以需要这样是因为`vdom`是一颗单根树形结构,`patch`方法在遍历的时候从根节点开始遍历,它要求只有一个根节点。组件也会转换为一个`vdom`,自然应该满足这个要求。
59+
- `vue3`中之所以可以写多个根节点,是因为引入了`Fragment`的概念,这是一个抽象的节点,如果发现组件是多根的,就创建一个Fragment节点,把多个根节点作为它的children。将来patch的时候,如果发现是一个Fragment节点,则直接遍历children创建或更新。
60+
61+
---
62+
63+
### 知其所以然
64+
65+
- patch方法接收单根vdom:
66+
67+
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L354-L355
68+
69+
```ts
70+
// 直接获取type等,没有考虑数组的可能性
71+
const { type, ref, shapeFlag } = n2
72+
```
73+
74+
- patch方法对Fragment的处理:
75+
76+
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L1091-L1092
77+
78+
```ts
79+
// a fragment can only have array children
80+
// since they are either generated by the compiler, or implicitly created
81+
// from arrays.
82+
mountChildren(n2.children as VNodeArrayChildren, container, ...)
83+
```
84+
85+

public/27-vuex-module/README.md

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
## 27-你有使用过vuex的module吗?
2+
3+
这是基本应用能力考察,稍微上点规模的项目都要拆分vuex模块便于维护。
4+
5+
---
6+
7+
### 体验
8+
9+
https://vuex.vuejs.org/zh/guide/modules.html
10+
11+
```js
12+
const moduleA = {
13+
state: () => ({ ... }),
14+
mutations: { ... },
15+
actions: { ... },
16+
getters: { ... }
17+
}
18+
const moduleB = {
19+
state: () => ({ ... }),
20+
mutations: { ... },
21+
actions: { ... }
22+
}
23+
const store = createStore({
24+
modules: {
25+
a: moduleA,
26+
b: moduleB
27+
}
28+
})
29+
store.state.a // -> moduleA 的状态
30+
store.state.b // -> moduleB 的状态
31+
store.getters.c // -> moduleA里的getters
32+
store.commit('d') // -> 能同时触发子模块中同名mutation
33+
store.dispatch('e') // -> 能同时触发子模块中同名action
34+
```
35+
36+
---
37+
38+
### 思路
39+
40+
1. 概念和必要性
41+
2. 怎么拆
42+
3. 使用细节
43+
4. 优缺点
44+
45+
---
46+
47+
### 范例
48+
49+
1. 用过module,项目规模变大之后,单独一个store对象会过于庞大臃肿,通过模块方式可以拆分开来便于维护
50+
2. 可以按之前规则单独编写子模块代码,然后在主文件中通过`modules`选项组织起来:`createStore({modules:{...}})`
51+
3. 不过使用时要注意访问子模块状态时需要加上注册时模块名:`store.state.a.xxx`,但同时`getters``mutations``actions`又在全局空间中,使用方式和之前一样。如果要做到完全拆分,需要在子模块加上`namespace`选项,此时再访问它们就要加上命名空间前缀。
52+
4. 很显然,模块的方式可以拆分代码,但是缺点也很明显,就是使用起来比较繁琐复杂,容易出错。而且类型系统支持很差,不能给我们带来帮助。pinia显然在这方面有了很大改进,是时候切换过去了。
53+
54+
---
55+
56+
### 可能的追问
57+
58+
1. 用过pinia吗?都做了哪些改善?

0 commit comments

Comments
 (0)