本文为原创文章,未获授权禁止转载,侵权必究!
本篇是 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