- A+
如何实现父组件监听子组件的生命周期
方法一:$emit
// 父组件 <template> <div> <Child @mounted="onMounted" @updated="onUpdated" @beforeDestroy="onBeforeDestroy" ></Child> </div> </template> // 子组件 ... mounted () { this.$emit('mounted') } updated () { this.$emit('updated') } beforeDestroy () { this.$emit('beforeDestroy') } ...
优点:简单易上手
缺点:此种方法子组件必须是自己编写的组件,若引用第三方库这种方式则不可行
方法二:@hook
// 父组件 <template> <div> <Child @hook:mounted="onMounted" @hook:updated="onUpdated" @hook:beforeDestroy="onBeforeDestroy" ></Child> </div> </template> // 子组件 <!--无-->
官方文档并没有太多相关解释,只在处理边界情况 #程序化的事件侦听器— Vue.js (vuejs.org)里有出现。
子组件无需相关处理就能实现侦听,这块的实现原理可以从源码里探究部分
在组件生命周期的每个函数内都调用了callHook它支持两个参入,分别是实例vm
和对应的生命周期钩子名称。而callHook
里面就执行了vm.$emit('hook:' + hook)
,此为方法一!
当在子组件上传入了对应的@hook:mounted
钩子,也就是执行了vm.$on('hook:mounted')
,而vue实例在生命周期里本身就会执行vm.$emit('hook:mounted')
,其实就连带着触发了我们绑定给子组件的回调函数了。
ps:使用callHook的好处
callHook(vm, 'beforeCreate')
调用后, const handlers = vm.$options[hook]
即读取到了当前 vm
实例上的任务队列,然后通过 for
循环依次传递给 invokeWithErrorHandling(handlers[i], vm, null, vm, info)
进行处理, 调用 invokeWithErrorHandling
如果发生异常, 则会统一报错处理。
拓展
1.hook:提升代码简洁性
在编写组件时,我们往往需要在各个生命周期里都针对某个业务逻辑做一些处理,业务散落在各个生命周期钩子里:
<script type="text/ecmascript-6"> export default { mounted () { // 挂载时执行一些业务A相关逻辑 // 挂载时执行一些业务B相关逻辑 } updated () { // 更新时执行一些业务A逻辑 // 更新时执行一些业务B逻辑 // 更新时执行一些业务C逻辑 } beforeDestroy () { // 销毁时执行一些业务A逻辑 // 销毁时执行一些业务C逻辑 } } </script>
业务逻辑散落在各个生命周期里,有时候是不利于我们阅读代码的,尤其是当该业务是一个复杂的长段代码时,这个时候我们就可以考虑利用hook:
来梳理某一块的业务代码,提升可阅读性:
<script type="text/ecmascript-6"> export default { created() { this.$on('hook:mounted', () => { 挂载时执行一些业务A相关逻辑 }) this.$on('hook:updated', () => { 挂载时执行一些业务A相关逻辑 }) this.$once('hook:beforeDestroy', () => { 挂载时执行一些业务A相关逻辑 }) } } </script>
这样就可以将散落的业务逻辑,都在一个created钩子函数里书写完毕,而且仍保持原来的生命周期逻辑。
2.避免data里的无用变量的定义
例如我们在编写组件时会执行一些事件监听或者定时器函数,我们希望在组件销毁的时候都能去销毁这些监听或者定时器,但是由于夸生命周期的问题,常常会将定时器赋值给一个全局变量或者绑定到this上,然后在另一个生命周期里获取并执行销毁操作。
// 优化前 <script type="text/ecmascript-6"> export default { data() { return { timer:null } } mounted () { this.timer = setInterval(() => { // todo }, 1000); } beforeDestroy () { clearInterval(this.timer) } } </script> // 优化后 <script type="text/ecmascript-6"> export default { mounted () { const timer = setInterval(() => { // todo }, 1000); this.$once('hook:beforeDestroy', function () { clearInterval(timer) }) } } </script>
Vue3.X
参考 https://v3-migration.vuejs.org/zh/breaking-changes/vnode-lifecycle-events.html