本文为原创文章,未获授权禁止转载,侵权必究!
本篇是 Vue3 源码解析系列第 13 篇,关注专栏
前言
我们知道 组件
分为 有状态组件
和 无状态组件
,上篇我们已经分析了 无状态组件
,本篇我们就来讲解下 有状态组件
的挂载更新。
案例
首先引入 h
、 render
函数,声明一个 component
包含 data
、render
函数的组件对象,通过 h
函数生成 组件 vnode
对象,之后通过 render
函数渲染该对象。
html
<!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)
}
}
const vnode = h(component)
render(vnode, document.querySelector('#app'))
</script>
</body>
</html>
render component
通过上篇我们得知 render
函数的组件挂载,会执行 patch
方法中 processComponent
的 mountComponent
方法:
ts
const mountComponent: MountComponentFn = (
initialVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
// 2.x compat may pre-create the component instance before actually
// mounting
const compatMountInstance =
__COMPAT__ && initialVNode.isCompatRoot && initialVNode.component
const instance: ComponentInternalInstance =
compatMountInstance ||
(initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
// 省略
// resolve props and slots for setup context
if (!(__COMPAT__ && compatMountInstance)) {
if (__DEV__) {
startMeasure(instance, `init`)
}
setupComponent(instance)
if (__DEV__) {
endMeasure(instance, `init`)
}
}
// 省略
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
)
// 省略
}
通过 createComponentInstance
方法获取到组件的实例,从而 组件
和 组件实例
形成一个双向绑定的关系,即 instance.vnode = vnode,vnode.component = instance
,之后执行 setupComponent
方法:
ts
export function setupComponent(
instance: ComponentInternalInstance,
isSSR = false
) {
isInSSRComponentSetup = isSSR
const { props, children } = instance.vnode
const isStateful = isStatefulComponent(instance)
initProps(instance, props, isStateful, isSSR)
initSlots(instance, children)
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)
: undefined
isInSSRComponentSetup = false
return setupResult
}
接着执行 isStatefulComponent
方法对 isStateful
赋值:
ts
export function isStatefulComponent(instance: ComponentInternalInstance) {
return instance.vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT
}
根据判断当前返回结果为 4
,之后执行 setupStatefulComponent
方法,由于 组件对象
中不存在 setup
属性,执行 finishComponentSetup
方法:
ts
export function finishComponentSetup(
instance: ComponentInternalInstance,
isSSR: boolean,
skipOptions?: boolean
) {
const Component = instance.type as ComponentOptions
// 省略
// template / render function normalization
// could be already set when returned from setup()
if (!instance.render) {
// 省略
instance.render = (Component.render || NOOP) as InternalRenderFunction
// 省略
}
// support for 2.x options
if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {
setCurrentInstance(instance)
pauseTracking()
applyOptions(instance)
resetTracking()
unsetCurrentInstance()
}
// 省略
}
该方法会将组件对象的 render
赋值给组件实例的 render
即 instance.render = Component.render
,之后执行 applyOptions
方法,该方法定义在 packages/runtime-core/src/componentOptions.ts
文件中:
ts
export function applyOptions(instance: ComponentInternalInstance) {
const options = resolveMergedOptions(instance)
const publicThis = instance.proxy! as any
// 省略
const {
// state
data: dataOptions,
computed: computedOptions,
methods,
watch: watchOptions,
provide: provideOptions,
inject: injectOptions,
// lifecycle
created,
beforeMount,
mounted,
beforeUpdate,
updated,
activated,
deactivated,
beforeDestroy,
beforeUnmount,
destroyed,
unmounted,
render,
renderTracked,
renderTriggered,
errorCaptured,
serverPrefetch,
// public API
expose,
inheritAttrs,
// assets
components,
directives,
filters
} = options
// 省略
if (dataOptions) {
// 省略
const data = dataOptions.call(publicThis, publicThis)
// 省略
if (!isObject(data)) {
__DEV__ && warn(`data() should return an object.`)
} else {
instance.data = reactive(data)
if (__DEV__) {
for (const key in data) {
checkDuplicateProperties!(OptionTypes.DATA, key)
// expose data on ctx during dev
if (!isReservedPrefix(key[0])) {
Object.defineProperty(ctx, key, {
configurable: true,
enumerable: true,
get: () => data[key],
set: NOOP
})
}
}
}
}
}
// 省略
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)
// 省略
}
该方法会先解构出组件实例对象的所有配置属性,即我们定义的 data
、render
、生命周期
等函数,这里我们只需关注两个 dataOptions
即 data
, 以及 render
函数。
之后通过判断 if (dataOptions)
获取到 data
值,当前 dataOptions
为我们传入的 data
函数,返回值为 { msg: 'hello component'}
,即 data
值为 { msg: 'hello component' }
。需要注意的是这里通过 call
来改变了 this
指向 dataOptions.call(publicThis, publicThis)
,这里的 publicThis
同上篇文章中讲到 proxyToUse
一样,也是一个 proxy
对象。
根据判断当前 data
为对象执行 instance.data = reactive(data)
,将组件实例的 data
赋值为 reactive
响应式数据。
接着遍历 data
对象将属性挂载到上下文中:
ts
if (__DEV__) {
for (const key in data) {
checkDuplicateProperties!(OptionTypes.DATA, key)
// expose data on ctx during dev
if (!isReservedPrefix(key[0])) {
Object.defineProperty(ctx, key, {
configurable: true,
enumerable: true,
get: () => data[key],
set: NOOP
})
}
}
}
当前执行上下文及 publicThis
对象中就包含 msg
属性:
由此我们可以得知 setupComponent
方法主要做了两件事:一是赋值组件实例的 render
,二是获取到 data
并通过 reactive
包装成响应式数据赋值给组件实例的 data
。
之后执行 setupRenderEffect
方法,通过上篇文章得知会执行 renderComponentRoot
来生成 subTree
:
ts
export function renderComponentRoot(
instance: ComponentInternalInstance
): VNode {
const {
type: Component,
vnode,
proxy,
withProxy,
props,
propsOptions: [propsOptions],
slots,
attrs,
emit,
render,
renderCache,
data,
setupState,
ctx,
inheritAttrs
} = instance
let result
let fallthroughAttrs
const prev = setCurrentRenderingInstance(instance)
if (__DEV__) {
accessedAttrs = false
}
try {
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
// withProxy is a proxy with a different `has` trap only for
// runtime-compiled render functions using `with` block.
const proxyToUse = withProxy || proxy
result = normalizeVNode(
render!.call(
proxyToUse,
proxyToUse!,
renderCache,
props,
setupState,
data,
ctx
)
)
fallthroughAttrs = attrs
} else {
// 省略
}
} catch (err) {
// 省略
}
// 省略
return result
}
通过 normalizeVNode
方法返回 vnode
对象对 result
赋值,我们再看下当前传入的参数,通过 call
改变 render
的 this
指向:
ts
render!.call(
proxyToUse,
proxyToUse!,
renderCache,
props,
setupState,
data,
ctx
)
由于 proxyToUse
对象同 publicThis
包含了 msg
属性:
render
函数中 return h('div', this.msg)
的 this.msg
就指向了 proxyToUse
的 msg
,最终 result
赋值为含有 hello component
的 vnode
对象:
subTree
赋值完成,之后通过 patch
方法进行挂载,最终页面呈现:
总结
有状态组件
渲染核心其实就是将render
函数中的this.xx
变成一个真数据,要达到该目的,就需要改变this
指向,改变方式就是在subTree
赋值的方法renderComponentRoot
中来改变this
。