vue3从入门到放弃-各api对比

  • 邢毅彪
  • 41 Minutes
  • 2020年12月25日

vue3从入门到放弃-各常用api对比

我们从vue2-3的生命周期, props, data, methods, watch, computed, lifehook等方面进行对比

###生命周期

作为组件化开发至关重要的生命周期钩子, 两个版本的区别如下:

1.vue3的生命周期

vue3生命周期

2.vue2生命周期

vue2生命周期

通过对比我们不难看出, 主要差一点在于初始化方式,vue2使用的是new Vue()这种方式,而vue3使用的是createApp这种方式,另外vue3还提供了一个彩蛋createApp返回的app我们可以链式调用,举个栗子:

1
2
3
4
Vue.createApp({})
.component('SearchInput', SearchInputComponent)
.directive('focus', FocusDirective)
.use(LocalePlugin)

还有一个区别点就是beforeDestroy和destroyed生命周期替换成了beforeUnmount和unmounted(像react看齐?),其实并不是,我理解的是对应创建时的mount。(之前的destroy有点angular的味道?)

生命周期里面,这些生命周期都是Options api语法对应的生命周期, 也就是我们之前vue2的那种写法。在composition api里面的对应关系如下:

Options API Hook inside setup
beforeCreate Not needed*
created Not needed*
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered

这里仅用mounted实例

1
2
3
4
5
6
7
8
export default defineComponent({
setup() {
// mounted
onMounted(() => {
console.log('Component is mounted!')
})
}
})

这里的errorCaptured和vue2的一样, 主要是增加了renderTracked和renderTriggered。从源码看, 应该是依赖收集和依赖改变触发的钩子,这里先挖个坑, 后面我们再填。

语法

除了生命周期钩子以外,还有很多语法在composition api里面也有对应的实现

data与methods

我们在vue2里面,组件内部状态,实现响应式的基础, 就是在data这个属性里先注册。在composition api的思想里, 不是我们不能用data, data依然是可用的, 但是更加提倡我们将每个逻辑都拆分,组合起来。 使得相应的逻辑能够更加的聚合,这里再挖一个坑,后面会单独开一篇来讲为什么vue3要这么做, 以及这么做带来了什么好处。

在composition api里,我们要如何声明一个响应式的内部状态呢?答案是ref和reactive,话不多说, 直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<template>
<div class="diff">
<p>{{name}}</p>
<div>
<p>oldCount</p>
<button @click="reduceOldCount">-</button>
<span>{{oldCount}}</span>
<button @click="addOldCount">+</button>
</div>
<div>
<p>count</p>
<button @click="reduceCount">-</button>
<span>{{count}}</span>
<button @click="addCount">+</button>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
export default defineComponent({
props: {
name: {
type: String,
required: true,
}
},
data() {
return {
oldCount: 0,
}
},
setup(props) { // 这里展示composition api
console.log(props)
const count = ref(0)
function addCount() {
count.value++
}
function reduceCount() {
if (count.value > 0)count.value--
}

return {
count,
addCount,
reduceCount,
}
},
methods: {
addOldCount() {
this.oldCount++
},
reduceOldCount() {
if (this.oldCount > 0) this.oldCount--
}
}
})
</script>

还是我们熟悉的计数器的例子, 这里是同时演示了vue2和vue3的写法, 不得不说vue api的向下兼容做的不错, 这里如果从vue2迁移过来,理论上js版本基本不需要做修改就可以使用(再埋个坑,后面可能会出一篇vue3的break change), 但是ts版本, 我要研究一下之前class语法的支持情况再更新本博客

Note: 这里不仅展示了响应式, 而且还顺带展示了methods的区别。

细心的童鞋可能已经看到, 说好了两种方式声明data,这里只用到了ref, 没有用到reactive。下面就来展示一下reactive的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<template>
<div class="diff">
<div>
<p>oldList</p>
<ul>
<li v-for="item in oldList" :key="item.id">{{item.text}}</li>
</ul>
</div>
<div>
<p>List</p>
<ul>
<li v-for="item in list" :key="item.id">{{item.text}}</li>
</ul>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref } from 'vue';
export default defineComponent({
data() {
return {
oldList: [
{
id: 1,
text: '萝卜',
},
{
id: 2,
text: '青菜',
},
{
id: 3,
text: '土豆',
},
{
id: 1,
text: '萝卜',
},
]
}
},
setup() { // 这里展示composition api
const list = reactive([
{
id: 1,
text: '苹果',
},
{
id: 2,
text: '香蕉',
},
{
id: 3,
text: '鸭梨',
},
{
id: 1,
text: '车厘子',
},
])
return {
list,
}
},
})
</script>

相信这里大家也很容易看出区别来,ref主要是用于给number, string等值类型包装一层value,只是在模板里vue有个优化, 会直接去拿ref.value。而reactice呢就是直接传入一个array, object等引用类型即可。我们不难大胆开脑洞, ref就是一个reactive的语法糖。

props

说完了data, 自然大家就想到了props, 其实上面的例子已经展示了props的基本用法, 但是之前我们有一种场景, 直接拿props给data赋值, 这种场景下, 新的composition api怎么搞定呢?且看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
<div class="diff">
<p>{{name}}</p>
<p>{{helloName}}</p>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, toRef } from 'vue';
export default defineComponent({
props: {
name: {
type: String,
required: true,
}
},
setup(props) { // 这里展示composition api
console.log(props)
const name = toRef(props, 'name') // or const { name } = toRefs(props)
const helloName = ref(`hello ${name.value}`)

return {
helloName,
}
},
})
</script>

这里有两种用法toRef,toRefs, 这两种用法的区别在哪呢?区别主要在于如果name是可选的,如果父组件不传, toRefs将不会被追踪到, 这个时候应该使用toRef, 其他必传的场景, 我们都应该使用toRefs,毕竟有了es6解构语法, 我们可以少些很多代码。

computed

计算属性,一直是我非常喜欢vue的一个设计, 组件层级的memo设计, 真的是非常的贴心和好用, 不但可以少些很多命令式的代码, 对于性能优化也是非常好用, 真的可以把vue的响应式优势展现的淋漓尽致。吹了这么多, 在vue3 composition api里怎么能少了这个重量级的角色呢, 直接看代码吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<template>
<div class="diff">
<div>
<p>oldCount</p>
<button @click="reduceOldCount">-</button>
<span>{{oldCount}}</span>
<span>{{doubleOldCount}}</span>
<button @click="addOldCount">+</button>
</div>
<div>
<p>count</p>
<button @click="reduceCount">-</button>
<span>{{count}}</span>
<span>{{doubleCount}}</span>
<button @click="addCount">+</button>
</div>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, reactive, ref, toRef, toRefs } from 'vue';
export default defineComponent({
props: {
name: {
type: String,
required: true,
}
},
data() {
return {
oldCount: 0,
}
},
setup(props) { // 这里展示composition api
const count = ref(0)
const doubleCount = computed(() => count.value * 2)

function addCount() {
count.value++
}
function reduceCount() {
if (count.value > 0)count.value--
}

return {
count,
addCount,
reduceCount,
doubleCount,
}
},
computed: {
doubleOldCount(): number {
return this.oldCount * 2
}
},
methods: {
addOldCount() {
this.oldCount++
},
reduceOldCount() {
if (this.oldCount > 0) this.oldCount--
}
}
})
</script>

从这个例子可以看出, 还是熟悉的配方, 还是熟悉的味道。

watch

既然提到了响应式, 怎么能不提watch这个强大的api呢, 在vue3里面watch被赋予了更加强大的能力

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<template>
<div class="diff">
<div>
<p>oldCount</p>
<button @click="reduceOldCount">-</button>
<span>{{oldCount}}</span>
<span>{{doubleOldCount}}</span>
<button @click="addOldCount">+</button>
</div>
<div>
<p>count</p>
<button @click="reduceCount">-</button>
<span>{{count}}</span>
<span>{{doubleCount}}</span>
<button @click="addCount">+</button>
</div>
<div>
<p>oldList</p>
<ul>
<li v-for="item in oldList" :key="item.id">{{item.text}}</li>
</ul>
</div>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, reactive, ref, toRef, toRefs, watch } from 'vue';
export default defineComponent({
props: {
name: {
type: String,
required: true,
}
},
data() {
return {
oldCount: 0,
}
},
setup(props) { // 这里展示composition api
const count = ref(0)
const doubleCount = computed(() => count.value * 2)

function addCount() {
count.value++
}
function reduceCount() {
if (count.value > 0)count.value--
}

watch(count, (newState, preState) => {
console.log('new-newState', newState)
console.log('new-preState', preState)
})

return {
count,
addCount,
reduceCount,
doubleCount,
}
},
computed: {
doubleOldCount(): number {
return this.oldCount * 2
}
},
methods: {
addOldCount() {
this.oldCount++
},
reduceOldCount() {
if (this.oldCount > 0) this.oldCount--
}
},
watch: {
oldCount(newState, preState) {
console.log('old-newState', newState)
console.log('old-preState', preState)
}
}
})
</script>

除了最基本的用法之外, compositon api watch支持watch好几个source, 如下:

1
2
3
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
/* ... */
})

watch方法的第三个参数可以传入watchOptions, 这个和vue2没有变化, 支持immediate和deep.

最最最重磅的是,watch本身使用watchEffect实现, 所以有以下几点Buff增强:

  1. 回调函数第三个参数会传入一个回调, 这个回调的调用时机类似于watchEffect, 在这个副作用变化之前和组件卸载的时候会先调用这个, 一般用于我们清楚副作用的时候使用, 而这个在原有的options api里是不提供的。

  2. 第三个参数的watchOptions是可以传入watchEffect的options, 有

    1
    2
    3
    4
    5
    interface WatchEffectOptions {
    flush?: 'pre' | 'post' | 'sync' // default: 'pre'
    onTrack?: (event: DebuggerEvent) => void
    onTrigger?: (event: DebuggerEvent) => void
    }

emit and emits

vue3里面, 子组件向父组件发射事件依然是经典的emit,只不过在这个基础上做了增加, 引入了emits,使得发射事件之前有了校验(校验仅仅只是一个warn, 并不能拦截事件发射)。直接看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<template>
<div class="diff">
<button @click="emitEventBySetup">emitEventBySetup</button>
<button @click="emitEventByMethods">emitEventByMethods</button>
<button @click="emitEventByOthers">emitEventByOthers</button>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, reactive, ref, toRef, toRefs, watch } from 'vue';
export default defineComponent({
emits: {
childEvent: (payload: { from?: 'setup' | 'methods' }) => {
const fromList = ['setup', 'methods']
return payload?.from && fromList.indexOf(payload?.from) > -1
}
},

setup(props, ctx) { // 这里展示composition api
function emitEventBySetup() {
ctx.emit('childEvent', { from: 'setup' })
}

return {
emitEventBySetup,
}
},
methods: {
emitEventByMethods() {
this.$emit('childEvent', { from: 'methods' })
},
emitEventByOthers() {
// eslint-disable-next-line
// @ts-ignore
this.$emit('childEvent', { from: 'others' })
}
},
})
</script>

结语

至此我们把常用的api都做了对比, 下一篇博客将介绍一下相比这些常用api, 稍微高阶api, 类似mixins, watchEffect等用法

访问量