本文为原创文章,未获授权禁止转载,侵权必究!
本篇是 Vue3 源码解析系列第 15 篇,关注专栏
前言
上篇我们分析了 组件生命周期 钩子函数是如何运行的,那 组件生命周期 钩子函数中是如何访问 响应式数据,以及组件生命周期中 响应式数据 改变是如何引起 视图 的改变呢?本篇我们就来一探究竟。
案例一
首先引入 h 、 render 函数,声明一个 component 包含 data 、render 函数以及 created、mounted 两个生命周期钩子函数的组件对象,钩子函数中打印出 data 中的 msg 数据,通过 h 函数生成 组件 vnode 对象,最后通过 render 函数渲染该对象。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../../../dist/vue.global.js"></script>
</head>
<body>
<div id="app"></div>
<script>
const { h, render } = Vue
const component = {
data() {
return {
msg: 'hello component'
}
},
render() {
return h('div', this.msg)
},
created() {
console.log('created', this.msg)
},
mounted() {
console.log('mounted', this.msg)
}
}
const vnode = h(component)
render(vnode, document.querySelector('#app'))
</script>
</body>
</html>
render component reactivity
我们重新再来看下 applyOptions 方法:
export function applyOptions(instance: ComponentInternalInstance) {
const options = resolveMergedOptions(instance)
// 省略
if (created) {
callHook(created, instance, LifecycleHooks.CREATED)
}
function registerLifecycleHook(
register: Function,
hook?: Function | Function[]
) {
if (isArray(hook)) {
hook.forEach(_hook => register(_hook.bind(publicThis)))
} else if (hook) {
register((hook as Function).bind(publicThis))
}
}
registerLifecycleHook(onBeforeMount, beforeMount)
registerLifecycleHook(onMounted, mounted)
registerLifecycleHook(onBeforeUpdate, beforeUpdate)
registerLifecycleHook(onUpdated, updated)
registerLifecycleHook(onActivated, activated)
registerLifecycleHook(onDeactivated, deactivated)
registerLifecycleHook(onErrorCaptured, errorCaptured)
registerLifecycleHook(onRenderTracked, renderTracked)
registerLifecycleHook(onRenderTriggered, renderTriggered)
registerLifecycleHook(onBeforeUnmount, beforeUnmount)
registerLifecycleHook(onUnmounted, unmounted)
registerLifecycleHook(onServerPrefetch, serverPrefetch)
// 省略
}
根据判断 if(created) 执行 callHook 方法:
function callHook(
hook: Function,
instance: ComponentInternalInstance,
type: LifecycleHooks
) {
callWithAsyncErrorHandling(
isArray(hook)
? hook.map(h => h.bind(instance.proxy!))
: hook.bind(instance.proxy!),
instance,
type
)
}
我们知道 callWithAsyncErrorHandling 实际执行的是 hook 方法即传入的 created 方法,这里通过 bind 改变了 this 指向,它指向 组件实例 的 proxy 对象上,该对象存在 msg 属性:
所以打印 this.msg 也就能够访问到 msg 数据,所以此时输出:
之后注册 mounted 钩子函数:
registerLifecycleHook(onMounted, mounted)
function registerLifecycleHook(
register: Function,
hook?: Function | Function[]
) {
if (isArray(hook)) {
hook.forEach(_hook => register(_hook.bind(publicThis)))
} else if (hook) {
register((hook as Function).bind(publicThis))
}
}
我们可以看到这里通过 bind 改变了 this 指向,即 mounted 函数 this 指向了 publicThis:
之后执行 mounted 钩子函数,this.msg 就能访问到 publicThis 中的 msg 数据,此时打印输出:
案例二
首先引入 h 、 render 函数,声明一个 component 包含 data 、render 函数以及 created 生命周期钩子函数的组件对象,钩子函数中设置两秒后来修改 msg 值,通过 h 函数生成 组件 vnode 对象,最后通过 render 函数渲染该对象。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../../../dist/vue.global.js"></script>
</head>
<body>
<div id="app"></div>
<script>
const { h, render } = Vue
const component = {
data() {
return {
msg: 'hello component'
}
},
render() {
return h('div', this.msg)
},
created() {
setTimeout(() => {
this.msg = '你好,世界'
}, 2000)
}
}
const vnode = h(component)
render(vnode, document.querySelector('#app'))
</script>
</body>
</html>
component reactivity update
render 函数在挂载组件时,通过 setupComponent 调用 applyOptions 方法会对 data 返回的对象通过 reactive 包装为响应式数据。之后执行 setupRenderEffect 方法,创建一个 ReactiveEffect 实例 effect,这是响应式的关键,对 componentUpdateFn 方法进行依赖收集,两秒后重新修改 msg 值,触发 setter 行为,再次执行 componentUpdateFn 方法:
const componentUpdateFn = () => {
if (!instance.isMounted) {
// 省略
instance.isMounted = true
// 省略
} else {
// 省略
const nextTree = renderComponentRoot(instance)
// 省略
const prevTree = instance.subTree
instance.subTree = nextTree
// 省略
patch(
prevTree,
nextTree,
// parent may have changed if it's in a teleport
hostParentNode(prevTree.el!)!,
// anchor may have changed if it's in a fragment
getNextHostNode(prevTree),
instance,
parentSuspense,
isSVG
)
if (__DEV__) {
endMeasure(instance, `patch`)
}
next.el = nextTree.el
if (originNext === null) {
// self-triggered update. In case of HOC, update parent component
// vnode el. HOC is indicated by parent instance's subTree pointing
// to child component's vnode
updateHOCHostEl(instance, nextTree.el)
}
// updated hook
if (u) {
queuePostRenderEffect(u, parentSuspense)
}
// onVnodeUpdated
if ((vnodeHook = next.props && next.props.onVnodeUpdated)) {
queuePostRenderEffect(
() => invokeVNodeHook(vnodeHook!, parent, next!, vnode),
parentSuspense
)
}
// 省略
}
}
第一次组件挂载完,isMounted 会设置为 true,所以直接走 else 逻辑。通过 renderComponentRoot 函数返回 vnode 对象赋值给 nextTree,由于 msg 数据已更改,所以此时 vnode 对象为:
之后通过 patch 方法更新渲染,此时页面呈现:
总结
组件生命周期钩子函数都是通过bind来改变了this指向,从而来获取响应式数据。组件响应式数据更改会通过ReactiveEffect对componentUpdateFn方法的依赖收集,当修改数据时,会再次触发componentUpdateFn方法,之后通过patch方法进行挂载更新。
Vue3 源码实现
Vue3 源码解析系列
- Vue3源码解析之 源码调试
- Vue3源码解析之 reactive
- Vue3源码解析之 ref
- Vue3源码解析之 computed
- Vue3源码解析之 watch
- Vue3源码解析之 runtime
- Vue3源码解析之 h
- Vue3源码解析之 render(一)
- Vue3源码解析之 render(二)
- Vue3源码解析之 render(三)
- Vue3源码解析之 render(四)
- Vue3源码解析之 render component(一)
- Vue3源码解析之 render component(二)
- Vue3源码解析之 render component(三)
- Vue3源码解析之 render component(四)
- Vue3源码解析之 render component(五)
- Vue3源码解析之 diff(一)
- Vue3源码解析之 diff(二)
- Vue3源码解析之 compiler(一)
- Vue3源码解析之 compiler(二)
- Vue3源码解析之 compiler(三)
- Vue3源码解析之 createApp