vue3从入门到放弃-各常用api对比
我们从vue2-3的生命周期, props, data, methods, watch, computed, lifehook等方面进行对比
###生命周期
作为组件化开发至关重要的生命周期钩子, 两个版本的区别如下:
1.vue3的生命周期

2.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增强:
回调函数第三个参数会传入一个回调, 这个回调的调用时机类似于watchEffect, 在这个副作用变化之前和组件卸载的时候会先调用这个, 一般用于我们清楚副作用的时候使用, 而这个在原有的options api里是不提供的。
第三个参数的watchOptions是可以传入watchEffect的options, 有
1 2 3 4 5
| interface WatchEffectOptions { flush?: 'pre' | 'post' | 'sync' 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等用法