12.3 组件实例与组件的生命周期
组件实例是维护组件运行信息的对象(状态、子树、是否已挂载、钩子);有了它才能区分挂载与更新,也才有地方挂载生命周期钩子。
一、为什么需要组件实例
- 12.2 里
patch(null, subTree, ...)的旧节点永远是null,组件每次都全新挂载; - 更新的正确做法是拿「本次的子树」与「上一次渲染的子树」打补丁,因此需要把上一次的
subTree保存下来; - 这个「保存组件运行期信息的对象」就是组件实例,它本质上是一个状态集合。
二、实例对象的三个属性
| 属性 | 含义 |
|---|---|
state | 组件自身状态,即 reactive(data()) |
isMounted | 布尔值,区分「首次挂载」与「更新」 |
subTree | 上一次渲染的子树,更新时作为旧 vnode 参与打补丁 |
const instance = {
state,
isMounted: false, // 初始为 false
subTree: null
}
// 把实例挂到 vnode 上,更新时(含 12.4 的被动更新)才能找回实例
vnode.component = instance
effect(() => {
const subTree = render.call(state, state)
// 检查组件是否已被挂载
if (!instance.isMounted) {
// 首次挂载:旧节点传 null
patch(null, subTree, container, anchor)
// 重点:置为 true,下次就走进更新分支
instance.isMounted = true
} else {
// 更新:用新子树与上一次渲染的子树打补丁
patch(instance.subTree, subTree, container, anchor)
}
// 记录本次的子树
instance.subTree = subTree
}, { scheduler: queueJob })
vnode.component = instance是后续一切更新的前提:组件实例只创建一次,之后的 vnode 都要能指向它。 实例应该尽量轻量,只在真正需要时往它上面加属性。
三、生命周期钩子的挂载与调用
生命周期函数就是组件选项对象上的函数:先从选项对象中取出,再在合适的时机调用。
const {
render, data,
beforeCreate, created,
beforeMount, mounted, beforeUpdate, updated
} = componentOptions
// beforeCreate:此时还没有 state
beforeCreate && beforeCreate()
const state = reactive(data())
const instance = { state, isMounted: false, subTree: null }
vnode.component = instance
// created:实例已创建,this 指向 state
created && created.call(state)
effect(() => {
const subTree = render.call(state, state)
if (!instance.isMounted) {
beforeMount && beforeMount.call(state)
patch(null, subTree, container, anchor)
instance.isMounted = true
mounted && mounted.call(state)
} else {
beforeUpdate && beforeUpdate.call(state)
patch(instance.subTree, subTree, container, anchor)
updated && updated.call(state)
}
instance.subTree = subTree
}, { scheduler: queueJob })
| 钩子 | 调用时机 |
|---|---|
beforeCreate | 执行 data() 之前 |
created | 实例创建完成、首次渲染之前 |
beforeMount | 首次 patch 之前 |
mounted | 首次挂载完成(isMounted = true)之后 |
beforeUpdate | 更新时 patch 之前 |
updated | 更新时 patch 之后 |
执行顺序:beforeCreate → created → beforeMount → mounted →(更新时)beforeUpdate → updated。
同一生命周期钩子在 mixins 等场景下可能存在多个,真实实现会把它们序列化为数组依次调用;本例只处理单个函数。 这版钩子的
this还是state,所以在钩子里访问不到 props;12.4.3 引入渲染上下文后统一改为renderContext。
四、测试
const MyComponent = {
data() { return { foo: 'hello world' } },
render() {
setTimeout(() => { this.foo = 'hello vue' }, 2000)
return { type: 'div', children: `foo 的值是: ${this.foo}` }
},
created() { console.log('created', this) },
updated() { console.log('updated', this) }
}
首次渲染只打印
created;2 秒后状态变化触发自更新,再打印updated。
相关
- 12.2 组件状态与自更新:把渲染任务包进 effect,实现组件自更新
- 12.4 props 与组件的被动更新:props 进入实例,父组件更新带动子组件更新