上文详细解析了模版编译generate
函数的内部实现,是如何拼接render
字符串的。
本文将回归到模版编译的上层方法中,解析如何执行render
函数生成VNode
对象,利用patch
函数将VNode
对象渲染成DOM
节点
执行renderString生成render函数
上文解析完generate
函数之后,回归到baseCompile
方法中,实际此方法的返回即是generate
函数的返回(包含ast
、code
等属性的对象)。回归到调用baseCompile
方法的compile
函数中,此函数也是直接返回baseCompile
函数的返回值。最后回归到调用compile
方法的compileToFunction
函数中
// compileToFunction函数中 模版编译完的后续实现
const render = (__GLOBAL__ ? new Function(code)() : new Function('Vue', code)(runtimeDom)) as RenderFunction
// mark the function as runtime compiled
;(render as InternalRenderFunction)._rc = true
return (compileCache[key] = render)
复制代码
后续的实现中将生成的render
字符串作为参数传入到new Funtion
中生成函数,然后执行这个函数。依上文最终得到的字符串,生成函数之后执行得到如下真正的render
方法:
function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createVNode: _createVNode, createTextVNode: _createTextVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(_Fragment, null, [
_createTextVNode(_toDisplayString(message) + " ", 1 /* TEXT */),
_createVNode("button", { onClick: modifyMessage }, "修改数据", 8 /* PROPS */, ["onClick"])
], 64 /* STABLE_FRAGMENT */)
}
}
复制代码
然后将render
函数的_rc
属性设为true
,将render
函数存入缓存对象compileCache
中,key
为template
模版字符串,value
为render
函数,并且返回render
函数。回归到finishComponentSetup
方法中
// finishComponentSetup方法模版编译之后的后续实现
instance.render = (Component.render || NOOP) as InternalRenderFunction
if (instance.render._rc) {
instance.withProxy = new Proxy(
instance.ctx,
RuntimeCompiledPublicInstanceProxyHandlers
)
}
// support for 2.x options ...
复制代码
将返回的render
函数赋值给instance
对象的render
属性。判断render._rc
为true
(在上面方法的结尾处赋值_rc
属性为true
),将instance.ctx
对象进行Proxy
代理,并将代理返回的对象赋值给instance.withProxy
属性,看下Proxy
代理的钩子对象(RuntimeCompiledPublicInstanceProxyHandlers
)的内部实现
export const RuntimeCompiledPublicInstanceProxyHandlers = extend({}, PublicInstanceProxyHandlers,
{
get(target: ComponentRenderContext, key: string) {
// fast path for unscopables when using `with` block
},
has(_: ComponentRenderContext, key: string) {
//...has
}
}
)
复制代码
此对象中主要是设置了get
和has
的陷阱钩子函数(后续使用到withProxy
属性触发钩子函数时再详细解析)。随着finishComponentSetup
函数的执行完成,最终回归到mountComponent
函数中(调用链setupComponent
-> setupStatefulComponent
-> handleSetupResult
-> finishComponentSetup
,这里相关方法的调用关系可以去看下文章二”双向数据绑定”),当setupComponent
函数执行完成之后,开始执行setupRenderEffect
函数,解析下此方法的内部实现
全局依赖activeEffect(reactiveEffect)
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
) => {
instance.update = effect(function componentEffect() {/*...*/},
__DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
}
复制代码
此方法主要是执行effect
方法,传参是componentEffect
函数 和 createDevEffectOptions(instance)
函数的返回值(一个包含{scheduler, allowRecurse: true, onTrack, onTrigger}
四个属性的对象,后续使用到具体属性时再详细解析),先来看下effect
方法的内部实现
// effect方法定义
export function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
if (isEffect(fn)) {
fn = fn.raw
}
const effect = createReactiveEffect(fn, options)
if (!options.lazy) {
effect()
}
return effect
}
复制代码
effect
方法内部主要是执行createReactiveEffect
函数,参数仍然是fn
和options
// createReactiveEffect方法定义
function createReactiveEffect<T = any>(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect<T> {
const effect = function reactiveEffect(): unknown {/* ... */} as ReactiveEffect
effect.id = uid++
effect.allowRecurse = !!options.allowRecurse
effect._isEffect = true
effect.active = true
effect.raw = fn
effect.deps = []
effect.options = options
return effect
}
复制代码
此函数内部定义了reactiveEffect
函数,并在函数上新增了一些属性(active=true
、deps=[]
)。然后将这个函数返回了。回归到上层的effect
函数中,effect
变量等于createReactiveEffect
函数的返回值,就是reactiveEffect
方法,然后判断options.lazy
(为false
,options
中并无lazy
属性),开始执行effect
方法(即reactiveEffect
函数)。看下reactiveEffect
函数内部的具体实现
const effectStack: ReactiveEffect[] = []
// cleanup 方法定义
function cleanup(effect: ReactiveEffect) {
const { deps } = effect
// createReactiveEffect函数执行时设置deps为[]
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect)
}
deps.length = 0
}
}
let activeEffect: ReactiveEffect | undefined
// reactiveEffect 函数内部实现
function reactiveEffect(): unknown {
if (!effect.active) {
// createReactiveEffect方法执行时已经将active属性置为true
return options.scheduler ? undefined : fn()
}
if (!effectStack.includes(effect)) {
cleanup(effect)
try {
enableTracking()
effectStack.push(effect)
activeEffect = effect
return fn()
} finally {
effectStack.pop()
resetTracking()
activeEffect = effectStack[effectStack.length - 1]
}
}
}
let shouldTrack = true
const trackStack: boolean[] = []
// enableTracking 方法定义
export function enableTracking() {
trackStack.push(shouldTrack)
shouldTrack = true
}
复制代码
reactiveEffect
内部判断全局的effectStack
数组中是否已经存在effect
方法,再调用cleanup
方法将effect.deps
清空(初始化时为空数组)。之后调用enableTracking
方法,此方法的作用是在全局跟踪数组trackStack
中添加全局标识位shouldTrack
(初始化为true
),并将shouldTrack
置为true
。回归到reactiveEffect
方法中,随后将effect
方法存入全局effectStack
数组中,并将effect
方法赋值给全局的activeEffect
变量。然后执行fn方法。fn
方法就是调用createReactiveEffect
函数传入的fn
,就是componentEffect
函数,看下此函数的内部实现。
执行render函数生成vnode对象
// componentEffect函数的内部实现
function componentEffect() {
if (!instance.isMounted) {
// 初始化为挂载过,所以isMounted属性为false
let vnodeHook: VNodeHook | null | undefined
const { el, props } = initialVNode
const { bm, m, parent } = instance
// ...
const subTree = (instance.subTree = renderComponentRoot(instance))
// ...
}
else {/*...*/}
}
复制代码
此方法内部主要是执行了renderComponentRoot
函数,传参为instance
对象
export function renderComponentRoot(
instance: ComponentInternalInstance
): VNode {
const {
type: Component,
vnode,
proxy,
withProxy,
props,
propsOptions: [propsOptions],
slots,
attrs,
emit,
render,
renderCache,
data,
setupState,
ctx
} = instance
let result
currentRenderingInstance = instance
// ...
try {
let fallthroughAttrs
if (vnode.shapeFlag/* 4 */ & ShapeFlags.STATEFUL_COMPONENT/* 4 */) {
const proxyToUse = withProxy || proxy
result = normalizeVNode(
render!.call(
proxyToUse,
proxyToUse!,
renderCache,
props,
setupState,
data,
ctx
)
)
fallthroughAttrs = attrs
} else {} //...
} catch(err) {
// ...
}
}
复制代码
renderComponentRoot
函数内部首先通过render!.call(proxyToUse, ...)
方法执行instance.render
函数(本文开头已经展示了依本例模版解析后的render
函数),传参是proxyToUse
(就是withProxy
对象)和renderCache
(空数组[]
),下面详细解析render
函数的执行过程:
1、整个函数体都在with(_ctx){}
中,如果对with的用法不熟悉,可以了解下。简单来说就是在with
花括号里面的属性不需要指定命名空间,会自动指向_ctx
对象;with(Proxy){key}
会触发Proxy
代理的has
钩子函数(_ctx
对象就是withProxy
对象,本文上面提到了withProxy
就是instance.ctx
对象通过Proxy
代理后的对象)
2、const { ... } = _Vue
对_Vue
对象进行结构,首先会触发_ctx
的has
钩子函数(因为ctx
上并没有_Vue
属性,这里就忽略,后续再详细解析has
钩子函数)。回顾到之前解析完成的render String
开头部分,定义const _Vue = Vue
,也就是_Vue
就是全局的Vue
对象。那解构出来的一系列方法就是全局的Vue
暴露的方法(toDisplayString, createVNode, createTextVNode, Fragment, openBlock, createBlock
)
执行openBlock函数(生成存放子vnode对象的数组)
3、后续执行render
函数中return
的内容。首先是执行openBlock
函数(无参数)
export const blockStack: (VNode[] | null)[] = []
let currentBlock: VNode[] | null = null
// openBlock 函数定义
export function openBlock(disableTracking = false) {
blockStack.push((currentBlock = disableTracking ? null : []))
}
复制代码
openBlock
函数内部给全局的currentBlock
变量赋值空数组[]
,然后将这个变量push
到另一个全局的空数组blockStack
中,即blockStack=[[]]
,后续创建VNode
会使用这个全局数组
解析动态数据(依赖收集至全局targetMap对象)
4、然后执行createBlock
方法,其中第三个参数是数组,数组的第一项是createTextVNode
函数的返回值,执行createTextVNode
函数时参数有两个,第一个参数是toDisplayString
方法的执行结果,参数是message
。这里因为with(_ctx){message}
会触发has钩子函数,看下has
钩子函数的具体内部实现
// RuntimeCompiledPublicInstanceProxyHandlers 对象中的get、has钩子函数的内部实现
get(target: ComponentRenderContext, key: string) {
// fast path for unscopables when using `with` block
if ((key as any) === Symbol.unscopables) {
return
}
return PublicInstanceProxyHandlers.get!(target, key, target)
},
has(_: ComponentRenderContext, key: string) {
const has = key[0] !== '_' && !isGloballyWhitelisted(key)
if (__DEV__ && !has && PublicInstanceProxyHandlers.has!(_, key)) {
warn(
`Property ${JSON.stringify(
key
)} should not start with _ which is a reserved prefix for Vue internals.`
)
}
return has
}
复制代码
判断属性名称
key
值不是以'_'
开头的,并且不是特定的一些字符串,类似Object
、Boolean
等(具体可以去看下isGloballyWhitelisted
方法的内部实现),此时key
值为message
,所以has
为true
之后获取
messgae
的值,_ctx.message
会触发get
钩子函数,先判断属性名是否等于Symbol.unscopables
,此时key
值为message
,所以执行PublicInstanceProxyHandlers
的get
方法。一起看下PublicInstanceProxyHandlers
内部get
方法的具体实现
// PublicInstanceProxyHandlers内部get钩子函数的具体实现
get({ _: instance }: ComponentRenderContext, key: string) {
const {
ctx,
setupState,
data,
props,
accessCache,
type,
appContext
} = instance
// ...
let normalizedProps
if (key[0] !== '$') {
const n = accessCache![key]
if (n !== undefined) {}
else if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
accessCache![key] = AccessTypes.SETUP // 0
return setupState[key]
}
// ...
}
}
复制代码
首先会对target
对象进行结构,获取'_'
属性的值,target
就是_ctx
对象,本文前面提到了_ctx
是instance.ctx
的Proxy
代理对象(关于instance.ctx
对象上的_
属性的值,在文章二”双向数据绑定”中提到了instance
对象的创建,并新增_
属性值为instance
)。回归到get
钩子函数中,判断属性名key
(message
)不是以'$'
开头的,并且不存在于instance
的accessCache
缓存对象中,再判断instance.setupState
属性不是空对象,并且message
存在于setupState
对象中(在文章三”双向数据绑定”中提到instance.setupState
就是setup
函数执行完成之后返回的结果再通过Proxy
代理的对象)。本例中setupState
不是空对象并且message
也是此对象的属性,所以设置accessCache[message] = 0
,最终返回setupState[message]
的值。
因为
setupState
对象是setup
函数返回值的Proxy
对象,所以执行setupState[message]
时会触发get
钩子函数
// unref方法定义
export function unref<T>(ref: T): T extends Ref<infer V> ? V : T {
return isRef(ref) ? (ref.value as any) : ref
}
// shallowUnwrapHandlers对象中 get钩子函数的实现
const shallowUnwrapHandlers: ProxyHandler<any> = {
get: (target, key, receiver) => unref(Reflect.get(target, key, receiver))
}
复制代码
首先通过Reflect方法获取setupState.message
的值(文章三”双向数据绑定”中解析了message
属性值是调用ref
方法返回的RefImpl
实例对象)。然后调用unref
方法,判断入参的__v_isRef
属性是否为true
,本例中message
符合,所以返回ref.value
(message.value
)。因为message
是RefImpl
的实例对象,所以获取属性时会触发get
钩子函数
// RefImpl类 内部的get钩子函数
get value() {
track(toRaw(this), TrackOpTypes.GET/* "get" */, 'value')
return this._value
}
const targetMap = new WeakMap<any, KeyToDepMap>()
// track方法定义
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (!shouldTrack || activeEffect === undefined) {
return
}
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect)
activeEffect.deps.push(dep)
if (__DEV__ && activeEffect.options.onTrack) {
activeEffect.options.onTrack({
effect: activeEffect,
target,
type,
key
})
}
}
}
复制代码
钩子函数内部先调用track
函数收集依赖,函数内部先判断,targetMap
(WeakMap对象)全局对象中是否存在target
属性(初始化挂载是不存在的),若不存在则执行targetMap.set(target, (depsMap = new Map()))
,设置key=target
,value=new Map()
(空的Map
对象),然后获取depsMap
(Map
对象)中key=message
的属性值,因为depsMap
是新建的空Map
对象,所以也不存在message
属性,固执行depsMap.set(key, (dep = new Set()))
,设置key=message
,value=new Set()
(空的Set
对象)。因为dep
是空的Set
对象,所以往dep
对象中新增activeEffect
全局变量(本文上述解析过activeEffect
就是reactiveEffect
函数),然后在reactiveEffect
方法的deps
数组中添加dep
对象(Set
对象)。后续在修改message
的值之后触发set
钩子函数时会执行依赖,更新DOM
createTextVNode创建文本vnode对象
5、回归到render
函数中,根据一系列的Proxy
代理得到message="测试数据"
(以本例为模版解析),开始执行toDisplayString('测试数据')
export const toDisplayString = (val: unknown): string => {
return val == null
? ''
: isObject(val)
? JSON.stringify(val, replacer, 2)
: String(val)
}
复制代码
toDisplayString
函数最终返回String(val)
就是'测试数据'
6、接着开始执行createTextVNode
函数,参数为"测试数据 "
和 1
// createTextVNode 函数定义
export function createTextVNode(text: string = ' ', flag: number = 0): VNode {
return createVNode(Text, null, text, flag)
}
复制代码
createTextVNode
函数内部调用createVNode
方法,参数为Symbol('Text')
、null
、'测试数据 '
、1
,在文章二”数据双向绑定”中已经简单解析了createVNode
方法的作用,主要是生成一个VNode
对象,在初始化执行app.mount
时会使用,初始化执行时type
是调用createApp
传入的参数。现在是用来生成一个文本节点,看下createVNode
内部的具体实现
首先根据
type
类型给shapeFlag
赋值,因为type
是Symbol('Text')
,所以shapeFlag=0
创建
vnode
对象,其中patchFlag
属性值为1
接着调用
normalizeChildren
函数,此函数主要是用来处理节点的children
属性
export function normalizeChildren(vnode: VNode, children: unknown) {
let type = 0
const { shapeFlag } = vnode
if (children == null) {}
else if (isArray(children)) {}
else if (typeof children === 'object') {}
else if (isFunction(children)) {}
else {
children = String(children)
// force teleport children to array so it can be moved around
if (shapeFlag & ShapeFlags.TELEPORT) {
type = ShapeFlags.ARRAY_CHILDREN
children = [createTextVNode(children as string)]
} else {
type = ShapeFlags.TEXT_CHILDREN // 8
}
}
vnode.children = children as VNodeNormalizedChildren
vnode.shapeFlag |= type
}
复制代码
因为本例中文本节点的子节点是字符串并且shapeFlag=0
,所以vnode.children='测试数据'
,vnode.shapeFlag = 0 | 8 = 8
。之后回归到createVNode
方法中,执行currentBlock.push(vnode)
将vnode
存放到全局数组currentBlock
中(本文上述解析openBlock
方法时将currentBlock
赋值为空数组),然后返回vnode
对象。
createVNode创建元素vnode对象
7、回归到render
方法中,执行完文本节点之后开始执行元素节点(button
节点,直接调用createVNode
方法),参数为"button"
, { onClick: modifyMessage }
, '修改数据'
, 8
。
根据
type
是字符串类型,所以shapeFlag
赋值为1
创建
vnode
对象,赋值props
属性为{ onClick: modifyMessage }
,patchFlag
为8
调用
normalizeChildren
处理children
属性,此元素节点的children
也是字符串,所以vnode.children='修改数据'
,vnode.shapeFlag = 1 | 8 = 9
,最后将vnode
对象存入currentBlock
数组中并返回vnode
对象
createBlock创建根vnode对象
8、回归到render
函数中,当中括号的两个方法(createTextVNode
、createVNode
)执行完成后,最后执行createBlock
方法生成根vnode
对象
export function createBlock(
type: VNodeTypes | ClassComponent,
props?: Record<string, any> | null,
children?: any,
patchFlag?: number,
dynamicProps?: string[]
): VNode {
const vnode = createVNode(
type,
props,
children,
patchFlag,
dynamicProps,
true /* isBlock: prevent a block from tracking itself */
)
// save current block children on the block vnode
vnode.dynamicChildren = currentBlock || (EMPTY_ARR as any)
// close block
closeBlock()
// a block is always going to be patched, so track it as a child of its
// parent block
if (shouldTrack > 0 && currentBlock) {
currentBlock.push(vnode)
}
return vnode
}
复制代码
在createBlock
内部首先是调用createVNode
方法创建vnode
节点,参数是Symbol('Fragment')
、null
、[文本vnode对象, 元素vnode对象]
、64
、true
createVNode
方法中首先根据type
是Symbol
类型,shapeFlag
赋值为0
。创建vnode
对象,patchFlag
值为64
执行
normalizeChildren
函数,处理children
属性时,因为children
是数组,所以vnode.shapeFlag = 0 | 16 = 16
因为传入的
isBlockNode=true
,所以不会执行currentBlock.push(vnode)
,最后返回vnode
对象。
回归到createBlock
函数中,将vnode.dynamicChildren
属性赋值为currentBlock
数组(数组中包含文本vnode
对象 和 元素vnode
对象两个元素,也就是vnode.children
)。然后执行closeBlock
export function closeBlock() {
blockStack.pop()
currentBlock = blockStack[blockStack.length - 1] || null
}
复制代码
该方法内将blockStack
数组中的最后一项移除。由本文上述解析可知,blockStack
数组中只有一个元素,就是currentBlock
数组,然后将currentBlock
赋值为null
。最后createBlock
方法返回vnode
对象,type
为Symbol('Fragment')
,children
数组包含两个vnode
对象,是type
为Symbol('Text')
的文本 和 type
为'button'
的元素。至此render
函数的解析已经完全结束了。
总结
本文主要是详细解析render
函数的执行过程,首先解析动态数据message
时触发get
钩子函数,调用track
方法进行依赖的收集(activeEffect
变量收集到全局的targetMap
对象中);然后调用createTextVNode
方法构建文本vnode
对象;调用createVNode
方法构建button
元素的vnode
对象;最后调用createBlock
方法构建根vnode
对象。后续将详细解析patch
方法利用生成的vnode
对象构建出真正的DOM
元素