vue3渲染流程2(setupRenderEffect)
setupRenderEffect, 涉及到响应式数据的东西的,建议先去看去看Reactivity的响应式数据包再来。
setupRenderEffect中实际用到的是一个叫componentUpdate的方法,传递給effect,创建响应,赋值給instance.update。
const prodEffectOptions = {
scheduler: queueJob
}
instance.update = effect(componentEffect, prodEffectOptions)
componentEffect
当前测试用例instance.mounted === undefinded,利用renderComponentRoot,生成subTree,subTree是根据instance.type来生成的。
renderComponentRoot
如果组件是ShapeFlags.STATEFUL_COMPONENT类型,调用instance.render(instance.withProxy || instance.proxy, instance.renderCache)生成subTree,检测instance.type.inheritAttrs !== false,inheriAttrs的初始默认值为undefided,再次检测subTree.shapeFlag是否属于ShapeFlags.ELEMENT类型 或者ShapeFlags.COMPONENT类型,如果是则通过cloneVNode内部调用mergeProps把subTree.props与subTree.attrs合并,是不是忘记了attrs是什么东西?在initProps中,根据当前组件vnode.props所遍历得到的,本质上是vnode.props。
如果组件是ShapeFlage.FUNCTIONAL_COMPONENT,调用instance.vnode.type生成,和ShapeFlags.STATEFUL_COMPONENT类型中的合并subTree.props与instance.attrs行为一致, 但是attrs = instance.type.props ? instance.attrs : getFallthroughAttrs(instance.attrs)。getFallthroughAttrs在API DOC中有描述,这里主要是筛选出key为class、style和on开头的key。
export function renderComponentRoot(
instance: ComponentInternalInstance
): VNode {
const {
type: Component, // 具名匹配 传递給Component
parent,
vnode, // instance 也传递了vnode 可以说是一个component的核心了吧
proxy,
withProxy,
props,
slots,
attrs,
emit,
renderCache
} = instance
let result
currentRenderingInstance = instance // 当前正在render的组件
if (__DEV__) {
accessedAttrs = false
}
try {
let fallthroughAttrs
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { // STATEFUL_COMPONENT渲染subtree
// withProxy is a proxy with a different `has` trap only for
// runtime-compiled render functions using `with` block.
const proxyToUse = withProxy || proxy
result = normalizeVNode(
instance.render!.call(proxyToUse, proxyToUse!, renderCache)
)
fallthroughAttrs = attrs
} else {
// FUNCTINAL
const render = Component as FunctionalComponent
// in dev, mark attrs accessed if optional props (attrs === props)
if (__DEV__ && attrs === props) {
markAttrsAccessed()
}
result = normalizeVNode(
render.length > 1
? render(
props,
__DEV__
? {
get attrs() {
markAttrsAccessed()
return attrs
},
slots,
emit
}
: { attrs, slots, emit }
)
: render(props, null as any /* we know it doesn't need it */)
)
fallthroughAttrs = Component.props ? attrs : getFallthroughAttrs(attrs)
}
// attr merging
// in dev mode, comments are preserved, and it's possible for a template
// to have comments along side the root element which makes it a fragment
let root = result // 先默认自身return的vnode为root组件
let setRoot: ((root: VNode) => void) | undefined = undefined
if (__DEV__) {
;[root, setRoot] = getChildRoot(result) // chilid1 undefinded
}
if ( // undefined !== false
Component.inheritAttrs !== false && // 默认为undefinded
fallthroughAttrs &&
Object.keys(fallthroughAttrs).length
) {
if (
root.shapeFlag & ShapeFlags.ELEMENT ||
root.shapeFlag & ShapeFlags.COMPONENT
) {
root = cloneVNode(root, fallthroughAttrs)
} else if (__DEV__ && !accessedAttrs && root.type !== Comment) {
const allAttrs = Object.keys(attrs)
const eventAttrs: string[] = []
const extraAttrs: string[] = []
for (let i = 0, l = allAttrs.length; i < l; i++) {
const key = allAttrs[i]
if (isOn(key)) {
// remove `on`, lowercase first letter to reflect event casing accurately
eventAttrs.push(key[2].toLowerCase() + key.slice(3))
} else {
extraAttrs.push(key)
}
}
if (extraAttrs.length) {
warn(
`Extraneous non-props attributes (` +
`${extraAttrs.join(', ')}) ` +
`were passed to component but could not be automatically inherited ` +
`because component renders fragment or text root nodes.`
)
}
if (eventAttrs.length) {
warn(
`Extraneous non-emits event listeners (` +
`${eventAttrs.join(', ')}) ` +
`were passed to component but could not be automatically inherited ` +
`because component renders fragment or text root nodes. ` +
`If the listener is intended to be a component custom event listener only, ` +
`declare it using the "emits" option.`
)
}
}
}
const parentScopeId = parent && parent.type.__scopeId
if (parentScopeId) {
root = cloneVNode(root, { [parentScopeId]: '' })
}
if (vnode.dirs) {
if (__DEV__ && !isElementRoot(root)) {
warn(
`Runtime directive used on component with non-element root node. ` +
`The directives will not function as intended.`
)
}
root.dirs = vnode.dirs
}
if (vnode.transition) {
if (__DEV__ && !isElementRoot(root)) {
warn(
`Component inside <Transition> renders non-element root node ` +
`that cannot be animated.`
)
}
root.transition = vnode.transition
}
// inherit ref
if (Component.inheritRef && vnode.ref != null) {
root.ref = vnode.ref
}
if (__DEV__ && setRoot) {
setRoot(root)
} else {
result = root
}
} catch (err) {
handleError(err, instance, ErrorCodes.RENDER_FUNCTION)
result = createVNode(Comment)
}
currentRenderingInstance = null // 完成整个render
return result
}
componentUpdate,根据instance.isMounted来判断是否已经挂载,如果没有挂载则调用instance.render, 调用instance.render的过程中,如果有响应式数据,将会track到当前instance.update(effect),最终生成subTree,把subTrree.props与instance.attrs合并。
看渲染流程1中的测试用例:
const node = root.children[0] as HTMLElement
expect(node.getAttribute('class')).toBe('c2 c0')
c2 和c0合并了,就是基于这一行代码:
subTree = cloneVNode(subTree, instance.attrs) // subTree.props与instance.attrs的合并
如果有父parent.type.parentScopeId,则拓展到subTree.props
const parentScopeId = parent && parent.type.__scopeId
if (parentScopeId) {
root = cloneVNode(root, { [parentScopeId]: '' })
}
接着触发使用API所创建的beforeMounted,与vnode.props.onVnodeBeforeMount(创建vnode的时候传入的):
invokeArrayFnc(instance.bm) // 触发使用composition API创建的beforeMount事件。
// onVnodeBeforeMount
if ((vnodeHook = props && props.onVnodeBeforeMount)) { // vnode.props.onVnodeBeforeMount
invokeVNodeHook(vnodeHook, parent, initialVNode)
}
再次进行patch:
patch(null, instance.subTree, container) // container 为 render中的第二个参数②,没有改变。
再来一次同样类型的patch循环,Child,经过同样的processComponent,不同的是Child的vnode是拥有props的。
注意区分vnode.type.props,这里没有vnode.type.props,所以全部vnode.props都杯合并到,由renderComponentRoot创建的subTree.props
接上,继续执行到第三次patch,简称patch3,所需要patch的vnode(上面的subTree),简称vnode3,vnode3拥有从所有上级所传递的props,因为vnode3.type === 'div',是真实的DOM标签,所以shapeFlags被标记成ELEMENT,执行processElement,创建'div'的HTMLDivElement,把vnode.props赋值給当前HTMLDivElement。
触发vnode.props.onVnodeBeforeMount钩子。
hostInsert(el, container),container为render中的第二个参数②,把该HTMLELEMENT插入到container中。
patch完成后,类似回溯的写法,从内往外依次执行,设置vnode.el = instance.subTree(ShapeFlags.COMPONENT都会赋值),触发instance.m的钩子(在组件setup中使用composition API Mounted方法传递进的事件)
当前测试用例整个初始化渲染就完成了。