之前已经有提到 Vue 实体的生命週期,现在我们来研究一下如果加上子元件的话整个生命週期会是怎么跑的。
建立父元件
使用 Vue 的 root 当作父元件:
<div id="app" style="background-color: green;"> root count: {{count}} <button @click="count++">+1 for root</button> <button @click="$destroy()">destroy root</button> ...</div>
设置 count
并变动其值以便测试 update
钩子设置 $destroy
事件 trigger 以便测试 destroy
钩子var vm = new Vue({ el: "#app", data: { count: 0 }, beforeCreate() { console.log("root beforeCreate"); }, created() { console.log("root create"); }, beforeMount() { console.log("root beforeMount"); }, mounted() { console.log("root mounted"); }, beforeUpdate() { console.log("root beforeUpdate"); }, updated() { console.log("root updated"); }, beforeDestroy() { console.log("root beforeDestroy"); }, destroyed() { console.log("root destroyed"); }});
在各个 hook 上设置 console.log
以便观察其触发的时机。
建立子元件
使用 Vue.component
注册元件。
Vue.component("child-component", { template: ` <div :style="style">{{$options.name}} count: {{count}} <button @click="count++" >+1 for child</button> <button @click="$destroy()" >destroy child</button> </div>`, data() { return { count: 0, style: "background-color: pink;" }; }, beforeCreate() { console.log(`${this.$options.name} beforeCreate`); }, created() { console.log(`${this.$options.name} create`); }, beforeMount() { console.log(`${this.$options.name} beforeMount`); }, mounted() { console.log(`${this.$options.name} mounted`); }, beforeUpdate() { console.log(`${this.$options.name} beforeUpdate`); }, updated() { console.log(`${this.$options.name} updated`); }, beforeDestroy() { console.log(`${this.$options.name} beforeDestroy`); }, destroyed() { console.log(`${this.$options.name} destroyed`); }});
与父元件相同使用 count
及 $destroy
观察 update
及 destroy
钩子,并在各个 hook 加上 console.log
。
将子元件加入父元件中
<div id="app" style="background-color: green;"> root count: {{count}} <button @click="count++">+1 for root</button> <button @click="$destroy()">destroy root</button> <child-component class="child" ref="childComponent" /></div>
父元件 mounted
结束后会是下面这样的结果:
"root beforeCreate""root create""root beforeMount""child-component beforeCreate""child-component create""child-component beforeMount""child-component mounted""root mounted"
父元件在 beforeMount
钩子执行完后会去做创建子元件的实体并且完成渲染后才会叫用 mounted
钩子。
接着按下父元件中的 +1 for root
按钮:
"root beforeUpdate""root updated"
会发现只有父元件的 update
钩子会被触发。
再来按下子元件中的 +1 for child
按钮:
"child-component beforeUpdate""child-component updated"
会发现子元件的更新也不会触发父元件的 update
钩子。
接着在父元件中按下 destroy root
按钮:
"root beforeDestroy""child-component beforeDestroy""child-component destroyed""root destroyed"
root 在执行 beforeDestroy
后会执行完 child 的 destroy
后再执行 destroyed
钩子。
接着因为 Vue 实体已经 destroy
,因此需要重新整理页面以恢复原本的状态。
重整后按下子元件的 destroy
:
"child-component beforeDestroy""child-component destroyed"
只有 child 的 destroy
钩子被触发。
父元件使用 ref 变动子元件 data
经由刚刚的测试我们知道父元件更新自身的数值是不会触发子元件的 update
钩子,那如果在父元件上使用 ref 变动子元件的 data 呢?
为了测试,在父元件中加上 sync child
按钮:
<div id="app" style="background-color: green;"> root count: {{count}} <button @click="count++">+1 for root</button> <button @click="$destroy()">destroy root</button> <button @click="sync">sync child</button> <child-component class="child" ref="childComponent" /></div>
var vm = new Vue({ ... methods: { sync() { this.$refs.childComponent.count = this.count; } }});
按下 sync child
后,结果如下:
"child-component beforeUpdate""child-component updated"
虽然是在 root 触发变动,但只有 child 的 update
会被执行,因为只有 child 的 data
有被变动。
使用 props 变动子元件 data
上面直接用 ref
修改子元件内部的 data
,这次使用 props
试试看。
使用原本的 count
会将父元件的 template
,为避免因此而触发父元件的 update
,在父元件中加入 parentCount
:
var vm = new Vue({ ... data: { count: 0, parentCount: 0 }, ...});
另外在 template
中加入 +1 for root parent count
按钮,并且将 parentCount
传入 child 中:
<div id="app" style="background-color: green;"> root count: {{count}} <button @click="count++">+1 for root</button> <button @click="parentCount++">+1 for root parent count</button> <button @click="$destroy()">destroy root</button> <child-component class="child" :parent-count="parentCount" /></div>
Vue.component("child-component", { ... props: ['parentCount'] ...});
按下后结果如下:
"root beforeUpdate""child-component beforeUpdate""child-component updated""root updated"
虽然 parentCount
没有变动 root 的模板,但依然触发了 update
钩子,由此可知,不管 data
有没有挂到画面上,变动 data
就会触发 update
钩子。
多个子元件
刚刚的例子只有一个子元件,现在来看看多个子元件会是怎么运作的:
var mixin = { template: ` <div :style="style">{{$options.name}} count: {{count}} <button @click="count++" >+1 for child</button> <button @click="$destroy()" >destroy child</button> </div>`, data() { return { count: 0, style: "background-color: pink;" }; }, props: ['parentCount'], watch: { parentCount(val) { this.count = val; } }, beforeCreate() { console.log(`${this.$options.name} beforeCreate`); }, created() { console.log(`${this.$options.name} create`); }, beforeMount() { console.log(`${this.$options.name} beforeMount`); }, mounted() { console.log(`${this.$options.name} mounted`); }, beforeUpdate() { console.log(`${this.$options.name} beforeUpdate`); }, updated() { console.log(`${this.$options.name} updated`); }, beforeDestroy() { console.log(`${this.$options.name} beforeDestroy`); }, destroyed() { console.log(`${this.$options.name} destroyed`); }};Vue.component("child-component", { mixins: [mixin],});Vue.component("child-two-component", { mixins: [mixin], data() { return { style: "background-color: yellow" }; }});Vue.component("child-three-component", { mixins: [mixin], data() { return { style: "background-color: gray" }; }});
将刚刚的 child options
拉出来变成 mixin
,并使用 mixin
定义子元件,为了区分使用不同的颜色当背景。
并且加进 root 中:
<div id="app" style="background-color: green;"> root count: {{count}} <button @click="count++">+1 for root</button> <button @click="parentCount++">+1 for root parent count</button> <button @click="$destroy()">destroy root</button> <div> <child-component class="child" :parent-count="parentCount" /> </div> <div> <child-two-component class="child" :parent-count="parentCount" /> </div> <div> <child-three-component class="child" :parent-count="parentCount" /> </div></div>
可以看到再渲染时的钩子触发顺序:
"root beforeCreate""root create""root beforeMount""child-component beforeCreate""child-component create""child-component beforeMount""child-two-component beforeCreate""child-two-component create""child-two-component beforeMount""child-three-component beforeCreate""child-three-component create""child-three-component beforeMount""child-component mounted""child-two-component mounted""child-three-component mounted""root mounted"
对各别 child 按下 +1 for child
:
"child-component beforeUpdate""child-component updated"
"child-two-component beforeUpdate""child-two-component updated"
"child-three-component beforeUpdate""child-three-component updated"
各个 child 是互不影响的,都只会触发自身的 update
钩子。
现在因为按下 +1
的关係,所有的 child 的 count
都是 1
,这时再按 +1 for root parent count
的话,结果如下:
"root beforeUpdate""root updated"
虽然 parentCount
变为 1
,但是因为跟 child count
的值相同,所以不会触发 update
。
这时再按下 +1 for root parent count
会看到 child 的 update
触发:
"root beforeUpdate""child-component beforeUpdate""child-two-component beforeUpdate""child-three-component beforeUpdate""child-three-component updated""child-two-component updated""child-component updated""root updated"
可以看到钩子依序触发 beforeUpdate
,再以反序触发 updated
。
多个子元件的 destroy
按下 root 的 destroy
按钮观察 destroy
的情形:
"root beforeDestroy""child-component beforeDestroy""child-component destroyed""child-two-component beforeDestroy""child-two-component destroyed""child-three-component beforeDestroy""child-three-component destroyed""root destroyed"
个别的子元件依序触发了 destory
。
DEMO
CODEPEN结论
之前介绍了各个生命週期的的定义及触发时机,这次介绍了元件间交互的生命週期,定义及交互都了解对于开发是很有帮助的,也可以减少叫用错误的问题。
同步发表于 勇者斗 Vue 龙