1 Vue2.x 生命周期回顾
beforeCreate ,在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。created ,在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前尚不可用。beforeMount ,在挂载开始之前被调用:相关的 render 函数首次被调用。mounted ,实例被挂载后调用,这时el 被新创建的vm.$el 替换了。 如果根实例挂载到了一个文档内的元素上,当mounted被调用时vm.$el 也在文档内。beforeUpdate ,数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。updated ,由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。activated ,被 keep-alive 缓存的组件激活时调用。deactivated ,被 keep-alive 缓存的组件停用时调用。beforeDestroy ,实例销毁之前调用。在这一步,实例仍然完全可用。destroyed ,实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。errorCaptured ,当捕获一个来自子孙组件的错误时被调用。
参考:https://cn.vuejs.org/v2/api/#选项-生命周期钩子
以下是整个生命周期图示:
参考:https://cn.vuejs.org/v2/guide/instance.html#生命周期图示
2 Vue3.x 生命周期变化
被替换
- beforeCreate -> setup()
- created -> setup()
重命名
- beforeMount -> onBeforeMount
- mounted -> onMounted
- beforeUpdate -> onBeforeUpdate
- updated -> onUpdated
- beforeDestroy -> onBeforeUnmount
- destroyed -> onUnmounted
- errorCaptured -> onErrorCaptured
新增的
新增的以下2个方便调试
- onRenderTracked
- onRenderTriggered
参考:https://vue-composition-api-rfc.netlify.com/api.html#lifecycle-hooks
特别说明
由于 Vue3.x 是兼容 Vue2.x 的语法的,因此为了保证 Vue2.x 的语法能正常在 Vue3.x 中运行,大部分 Vue2.x 的回调函数还是得到了保留。比如:虽然
但是,以下2个生命周期钩子函数被改名后,在 Vue3.x 中将不会再有
- beforeDestroy -> onBeforeUnmount
- destroyed -> onUnmounted
另外,假如 Vue3.x 在 Q2 如期 Release 的话,大家一定要注意,在混合使用 Vue2.x 和 Vue3.x 语法的时候,特别要注意这2套API的回调函数的执行顺序。
3 Vue2.x + Composition API 对比 Vue3.x 生命周期执行顺序
如果大家看了我上一篇文章 VUE 3.0 学习探索入门系列 - 纠结要不要升级到Vue3.0?该如何升级?(5),我说过当 Vue3.x 正式 Realease 以后,我可能会先使用
那我今天主要关心的一个问题,一旦我使用
先测试下生命周期函数的执行顺序。
3.1 Vue2.x + Composition API 生命周期执行顺序
如下示例,在 Vue2.x 中引入兼容包 Composition API,然后Vue2.x 和 Vue3.x 的生命周期函数混合使用。
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 83 84 85 86 87 88 89 90 91 92 | <template> <div> <p><center>[wp_ad_camp_4]</center></p><p> {{ id }} </p> <p> {{ name }} </p> </div> </template> <script> import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from '@vue/composition-api'; export default { setup() { const id = ref(1) console.log('setup') onBeforeMount(() => { console.log('onBeforeMount') }) onMounted(() => { console.log('onMounted') }) onBeforeUpdate(() => { console.log('onBeforeUpdate') }) onUpdated(() => { console.log('onUpdated') }) onBeforeUnmount(() => { console.log('onBeforeUnmount') }) onUnmounted(() => { console.log('onUnmounted') }) // 测试 update 相关钩子 setTimeout(() => { id.value = 2 }, 2000) return { id } }, data() { console.log('data') return { name: 'lilei' } }, beforeCreate() { console.log('beforeCreate') }, created() { console.log('created') }, beforeMount() { console.log('beforeMount') }, mounted() { console.log('mounted') setTimeout(() => { this.id = 3; }, 4000) }, beforeUpdate() { console.log('beforeUpdate') }, updated() { console.log('updated') }, beforeUnmount() { }, unmounted() { console.log('unmounted') }, beforeDestroy() { console.log('beforeDestroy') }, destroyed() { console.log('destroyed') } } </script> |
执行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 1. beforeCreate 2. setup 3. data 4. created 5. beforeMount 6. onBeforeMount 7. mounted 8. onMounted 9. beforeUpdate 10. onBeforeUpdate 11. updated 12. onUpdated 13. beforeDestroy 14. onBeforeUnmount 15. destroyed 16. onUnmounted |
结论
在
3.2 Vue3.x 生命周期执行顺序
以下直接使用 Vue3.x 语法,看看其在兼容 Vue2.x 情况下,生命周期回调函数混合使用的执行顺序。
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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | <template> <div> <p> {{ id }} </p> <p> {{ name }} </p> </div> </template> <script> import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, onRenderTracked, onRenderTriggered } from 'vue'; export default { setup() { const id = ref(1) console.log('setup') onBeforeMount(() => { console.log('onBeforeMount') }) onMounted(() => { console.log('onMounted') }) onBeforeUpdate(() => { console.log('onBeforeUpdate') }) onUpdated(() => { console.log('onUpdated') }) onBeforeUnmount(() => { console.log('onBeforeUnmount') }) onUnmounted(() => { console.log('onUnmounted') }) onRenderTracked(() => { console.log('onRenderTracked') }) onRenderTriggered(() => { console.log('onRenderTriggered') }) // 测试 update 相关钩子 setTimeout(() => { id.value = 2; }, 2000) return { id } }, data() { console.log('data') return { name: 'lilei' } }, beforeCreate() { console.log('beforeCreate') }, created() { console.log('created') }, beforeMount() { console.log('beforeMount') }, mounted() { console.log('mounted') setTimeout(() => { this.id = 3; }, 4000) }, beforeUpdate() { console.log('beforeUpdate') }, updated() { console.log('updated') }, beforeUnmount() { console.log('beforeUnmount') }, unmounted() { console.log('unmounted') } } </script> <style scoped> </style> |
执行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 1. beforeCreate 2. data 3. created 4. onRenderTracked 5. onRenderTracked 6. onBeforeMount 7. beforeMount 8. onMounted 9. mounted 10. onRenderTriggered 11. onRenderTracked 12. onRenderTracked 13. onBeforeUpdate 14. beforeUpdate 15. onUpdated 16. updated 17. onBeforeUnmount 18. beforeUnmount 19. onUnmounted 20. unmounted |
结论
在
4 Vue2.x + Composition API 过度到 Vue3.x 生命周期总结
综上所述:
- 在
Vue2.x 中通过补丁形式引入Composition API ,进行Vue2.x 和Vue3.x 的回调函数混用时:Vue2.x 的回调函数会相对先执行,比如:mounted 优先于onMounted 。 - 在
Vue3.x 中,为了兼容Vue2.x 的语法,所有旧的生命周期函数得到保留(除了beforeDestroy 和destroyed )。当生命周期混合使用时:Vue3.x 的生命周期相对优先于 Vue2.x 的执行,比如:onMounted 比mounted 先执行。
通过对比可以得出:当你的主版本是哪个,当生命周期混用时,谁的回调钩子就会相对优先执行。
所以,这里就会有点坑!为了给减小以后不必要的麻烦,如果大家在
- 不要混用Vue2.x和Vue3.x的生命周期。要么你继续使用 Vue2.x 的钩子函数,要么使用 Vue3.x 的钩子函数,这样就没问题。
- 在原则1的情况下,建议源码从工程或者目录就区分开新老版本。方便以后升级或者被引入到 Vue3.x 使用的时候,更有针对性兼容测试。
5 Composition API 核心语法
以下内容,大部分参考官方: Vue Composition API
5.1 setup 主执行函数
setup 就是将 Vue2.x 中的beforeCreate 和created 代替了,以一个setup 函数的形式,可以灵活组织代码了。setup 还可以 return 数据或者 template,相当于把data 和render 也一并代替了!
为什么说
下面看看其几个核心特点:
1 返回一组数据给template
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <template> <div>{{ count }} {{ object.foo }}</div> </template> <script> import { ref, reactive } from 'vue' export default { setup() { const count = ref(0) const object = reactive({ foo: 'bar' }) // expose to template return { count, object } } } </script> |
2 使用jsx语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <script> import { h, ref, reactive } from 'vue' export default { setup() { const count = ref(0) const object = reactive({ foo: 'bar' }) return () => h('div', [ h('p', { style: 'color: red' }, count.value), h('p', object.foo) ]) } } </script> |
3 Typescript Type
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | interface Data { [key: string]: unknown } interface SetupContext { attrs: Data slots: Slots emit: ((event: string, ...args: unknown[]) => void) } function setup( props: Data, context: SetupContext ): Data |
4 参数
需要注意的是,在
-
由于
setup 是一个入口函数,本质是面向函数编程了,而this 是面向对象的一种体现!在完全基于函数的编程世界中,这个this 就很难在能达到跟 Vue2.x 那种基于 OOP 思想的 Options 机制的实现的效果。 -
同样是基于函数式编程,那么如果加上 this,对于新手而言,本来 this 就不好理解,这时候就更加懵逼了。比如:
1
2
3
4
5setup() {
function onClick() {
this // 如果有 this,那么这里的 this 可能并不是你期待的!
}
}
取消了
- props,组件参数
- context,上下文信息
1 2 3 4 5 6 | setup(props, context) { // props // context.attrs // context.slots // context.emit } |
也许你会有疑问,仅有这2个参数就够了么?够了。你在 Vue2.x 的时候,
其实,这2个参数都是外部引入的,这个没办法只能带入初始化函数中。除此之外,你组件上用到的所有 this 能获取的数据,现在都相当于在 setup 中去定义了,相当于局部变量一样,你还要 this 干嘛呢?
比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | setup(props, context) { // data const count = ref(1) // 生命周期钩子函数 onMounted(() => { console.log('mounted!') }) // 计算函数 const plusOne = computed(() => count.value + 1) // methods 方法 const testMethod = () => { console.log('methods'); } // return to template return { count, testMethod } } |
一切在
当然,如果你要讲 Vue2.x 和 Vue3.x 混用!那就很别扭了,以后用 this,以后又不能用,你自己也会懵逼,所以在此建议:虽然Vue3.x兼容Vue2.x语法,但是不建议混合使用各自语法!
5.2 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 | <template> <div> <p>{{ obj1.cnt }}</p> <p>{{ obj2.cnt }}</p> </div> </template> <script> import { reactive } from 'vue' setup() { // 普通对象 const obj1 = { cnt: 1 } // 代理对象 const obj2 = reactive({ cnt: 1 }) obj1.cnt++ obj2.cnt++ return { obj1, obj2 } } </script> |
页面显示结果:
1 2 | 1 2 |
可以看到,普通对象属性更新时,页面是不会同步更新的。只有代理对象,才可以实现双向绑定。
5.3 ref 方法
被
也就是说
比如:
1 2 3 | setup() { const count = ref(100) } |
被
1 2 3 4 5 | setup() { const count = reactive({ value: 100 }) } |
因为变成了一个代理对象,所以取值的时候需要
1 2 3 4 | setup() { const count = ref(100) console.log(count.value) // output: 100 } |
另外
1 2 3 4 5 6 7 8 9 10 11 12 13 | <template> <div>{{ count }}</div> </template> <script> export default { setup() { return { count: ref(0) } } } </script> |
以下是一些基本元素
1 2 3 4 5 6 7 8 | setup() { console.log(ref(100).value) // output: 100 console.log(ref('test').value) // output: test console.log(ref(true).value) // output: true console.log(ref(null).value) // output: null console.log(ref(undefined).value) // output: undefined console.log(ref({}).value) // output: {} } |
5.3 isRef 方法
判断一个对象是否
1 | const unwrapped = isRef(foo) ? foo.value : foo |
5.4 toRefs 方法
将一个
看看下面的例子你可能就明白它的作用了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <template> <p>{{ obj.count }}</p> <p>{{ count }} <p><center>[wp_ad_camp_5]</center></p><p>{{ value }} </template> <script> export default { setup() { const obj = reactive({ count: 0, value: 100 }) return { obj, // 如果这里的 obj 来自另一个文件, // 这里就可以不用包裹一层 key,可以将 obj 的元素直接平铺到这里 // template 中可以直接获取属性 ...toRefs(obj) } } } </script> |
5.5 computed 函数
与 Vue2.x 中的作用类似,获取一个计算结果。当然功能有所增强,不仅支持取值
注意: 结果是一个
正常获取一个计算结果:
1 2 3 4 5 6 | const count = ref(1) const plusOne = computed(() => count.value + 1) console.log(plusOne.value) // 2 plusOne.value++ // 报错,因为未实现 set 函数,无法赋值操作! |
当 computed 参数使用 object 对象书写时,使用 get 和 set 属性。set 属性可以将这个对象编程一个可写的对象。
也就是说
1 2 3 4 5 6 7 8 9 | const count = ref(1) const plusOne = computed({ get: () => count.value + 100, set: val => { count.value = val - 1 } }) plusOne.value = 1 console.log(count.value) // 0 console.log(plusOne.value) // 100 |
5.5 readonly 函数
使用
返回的 readonly 对象,一旦修改就会在 console 有 warning 警告。程序还是会照常运行,不会报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | const original = reactive({ count: 0 }) const copy = readonly(original) watchEffect(() => { // 只要有数据变化,这个函数都会执行 console.log(copy.count) }) // 这里会触发 watchEffect original.count++ // 这里不会触发上方的 watchEffect,因为是 readonly。 copy.count++ // warning! |
还有一些 API 诸如
6 最后
本文也是
接下来就等 Vue3.0 正式 Release 以后,再带给大家
本系列历时20天完成,再次感谢大家。
(全剧终)