上文说到mountComponent的主要核心就是 createComponentInstance,setupComponent,setupRenderEffect 这三个函数的调用,而setupRenderEffect 则是组件依赖收集,更新的关键,而setupRenderEffect 依靠的就是
本文主要讨论setupRenderEffect 如何使用componentUpdateFn以及reativeEffect
目录
1.setupRenderEffect
setupRenderEffect会先根据componentUpdateFn创建一个reativeEffect 实例,并赋值给instance.effect, effect ,然后声明函数effect.run,并赋值给update以及instance.update,最后调用update函数
//调用render,进行组件的依赖收集
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
) => {
const componentUpdateFn = () => {
if (!instance.isMounted) {
// 此时activeEffect为当前的effect
// 组件初始化的时候会执行这里
// 为什么要在这里调用 render 函数呢
// 是因为在 effect 内调用 render 才能触发依赖收集
// 等到后面响应式的值变更后会再次触发这个函数
let vnodeHook: VNodeHook | null | undefined
const { el, props } = initialVNode
const { bm, m, parent } = instance
const isAsyncWrapperVNode = isAsyncWrapper(initialVNode)
toggleRecurse(instance, false)
// beforeMount hook
if (bm) {
invokeArrayFns(bm)
}
// onVnodeBeforeMount
if (
!isAsyncWrapperVNode &&
(vnodeHook = props && props.onVnodeBeforeMount)
) {
invokeVNodeHook(vnodeHook, parent, initialVNode)
}
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
instance.emit('hook:beforeMount')
}
toggleRecurse(instance, true)
if (el && hydrateNode) {
// vnode has adopted host node - perform hydration instead of mount.
const hydrateSubTree = () => {
if (__DEV__) {
startMeasure(instance, `render`)
}
//根渲染节点赋值到subTree---里面执行render函数(进行依赖的收集)
instance.subTree = renderComponentRoot(instance)
if (__DEV__) {
endMeasure(instance, `render`)
}
if (__DEV__) {
startMeasure(instance, `hydrate`)
}
hydrateNode!(
el as Node,
instance.subTree,
instance,
parentSuspense,
null
)
if (__DEV__) {
endMeasure(instance, `hydrate`)
}
}
if (isAsyncWrapperVNode) {
; (initialVNode.type as ComponentOptions).__asyncLoader!().then(
// note: we are moving the render call into an async callback,
// which means it won't track dependencies - but it's ok because
// a server-rendered async wrapper is already in resolved state
// and it will never need to change.
() => !instance.isUnmounted && hydrateSubTree()
)
} else {
hydrateSubTree()
}
} else {
if (__DEV__) {
startMeasure(instance, `render`)
}
//根渲染节点赋值到subTree---里面执行render函数
//1.执行render函数,创建虚拟子节点
//2.进行数据的依赖收集(setup中的数据进行了proxy代理)
//3.在render中通过this访问instance.proxy,instance.proxy对数据访问进行不同proxy处理
const subTree = (instance.subTree = renderComponentRoot(instance))
if (__DEV__) {
endMeasure(instance, `render`)
}
if (__DEV__) {
startMeasure(instance, `patch`)
}
// 基于 subTree 再次调用 patch
// 基于 render 返回的 vnode ,再次进行渲染
// 递归的开箱(对于render的虚拟节点进行patch)
patch(
null,
subTree,
container,
anchor,
instance,
parentSuspense,
isSVG
)
if (__DEV__) {
endMeasure(instance, `patch`)
}
// 把 root element 赋值给 组件的vnode.el ,为后续调用 $el 的时候获取值
// 是为了将组件和组件内部的根节点联系起来,以便正确地进行组件的初始化和挂载。
initialVNode.el = subTree.el
}
// mounted hook
if (m) { //mount放在post类型cheduler进行异步处理
queuePostRenderEffect(m, parentSuspense)
}
// onVnodeMounted
if (
!isAsyncWrapperVNode &&
(vnodeHook = props && props.onVnodeMounted)
) {
const scopedInitialVNode = initialVNode
queuePostRenderEffect(
() => invokeVNodeHook(vnodeHook!, parent, scopedInitialVNode),
parentSuspense
)
}
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
queuePostRenderEffect(
() => instance.emit('hook:mounted'),
parentSuspense
)
}
// activated hook for keep-alive roots.
// #1742 activated hook must be accessed after first render
// since the hook may be injected by a child keep-alive
if (
initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE ||
(parent &&
isAsyncWrapper(parent.vnode) &&
parent.vnode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE)
) {
instance.a && queuePostRenderEffect(instance.a, parentSuspense)
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
queuePostRenderEffect(
() => instance.emit('hook:activated'),
parentSuspense
)
}
}
instance.isMounted = true
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
devtoolsComponentAdded(instance)
}
// #2458: deference mount-only object parameters to prevent memleaks
initialVNode = container = anchor = null as any
} else {
//更新组件逻辑
// updateComponent
// This is triggered by mutation of component's own state (next: null)
// OR parent calling processComponent (next: VNode)
let { next, bu, u, parent, vnode } = instance
let originNext = next
let vnodeHook: VNodeHook | null | undefined
if (__DEV__) {
pushWarningContext(next || instance.vnode)
}
// Disallow component effect recursion during pre-lifecycle hooks.
toggleRecurse(instance, false)
// 如果有 next 的话, 说明需要更新组件的数据(props,slots 等)
// 先更新组件的数据,然后更新完成后,在继续对比当前组件的子元素
if (next) {
//父组件更新调用processComponet触发子组件更新
//新组件 next 的 el 属性指向老组件 vnode 的 el 属性
// 让新组件直接使用旧组件的 el 对象
//instance.next = n2----updateComponent函数
//说明next只是虚拟节点,next此时并没有el属性
next.el = vnode.el
updateComponentPreRender(instance, next, optimized)
} else {
//组件自身的状态变化,触发组件更新
next = vnode
}
// beforeUpdate hook
if (bu) {
invokeArrayFns(bu)
}
// onVnodeBeforeUpdate
if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) {
invokeVNodeHook(vnodeHook, parent, next, vnode)
}
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
instance.emit('hook:beforeUpdate')
}
toggleRecurse(instance, true)
// render
if (__DEV__) {
startMeasure(instance, `render`)
}
// 调用render函数--重新进行依赖收集和节点创建并赋值给nextTree
const nextTree = renderComponentRoot(instance)
if (__DEV__) {
endMeasure(instance, `render`)
}
const prevTree = instance.subTree
instance.subTree = nextTree
if (__DEV__) {
startMeasure(instance, `patch`)
}
//patch prevTree和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) { //updated在post类型scheduler进行异步处理
queuePostRenderEffect(u, parentSuspense)
}
// onVnodeUpdated
if ((vnodeHook = next.props && next.props.onVnodeUpdated)) {
queuePostRenderEffect(
() => invokeVNodeHook(vnodeHook!, parent, next!, vnode),
parentSuspense
)
}
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
queuePostRenderEffect(
() => instance.emit('hook:updated'),
parentSuspense
)
}
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
devtoolsComponentUpdated(instance)
}
if (__DEV__) {
popWarningContext()
}
}
}
// 在effect内部调用componentUpdateFn---进行依赖收集
// create reactive effect for rendering
// 第三个参数instance.scope----ReactiveEffect需要一个 scope 参数来收集所有的 effect
const effect = (instance.effect = new ReactiveEffect(
componentUpdateFn,
() => queueJob(update), //传入一个scheduler调度器--用于组件更新
instance.scope // track it in component's effect scope
))
//将() => effect.run()同时赋值到instance.updte,update
//通过使用instance.updte,update触发effect.run
const update: SchedulerJob = (instance.update = () => effect.run())
update.id = instance.uid
// allowRecurse
// #1801, #2043 component render effects should allow recursive updates
toggleRecurse(instance, true)
if (__DEV__) {
effect.onTrack = instance.rtc
? e => invokeArrayFns(instance.rtc!, e)
: void 0
effect.onTrigger = instance.rtg
? e => invokeArrayFns(instance.rtg!, e)
: void 0
update.ownerInstance = instance
}
// 调用update
update()
}
1.1 ReactiveEffect (响应式对象)
ReactiveEffect是一个类,最关键的就是 run方法,因为setupRenderEffect 调用的就是run方法,
run方法最主要的将activeEffect赋值给自己,然后return this.fn(), 就是执行fn函数也就是传过来的componentUpdateFn,
对于ReactiveEffect,我们先暂时过一下,先看componentUpdateFn
export class ReactiveEffect<T = any> {
active = true
//dep 数组,在响应式对象收集依赖时也会将对应的依赖项添加到这个数组中
deps: Dep[] = []
parent: ReactiveEffect | undefined = undefined
/**
* Can be attached after creation
* @internal
*/
computed?: ComputedRefImpl<T>
/**
* @internal
*/
allowRecurse?: boolean
/**
* @internal
*/
private deferStop?: boolean
onStop?: () => void
// dev only
onTrack?: (event: DebuggerEvent) => void
// dev only
onTrigger?: (event: DebuggerEvent) => void
constructor(
public fn: () => T,
public scheduler: EffectScheduler | null = null,
scope?: EffectScope
) {
recordEffectScope(this, scope)
}
run() {
// 当active=false,执行函数但不收集依赖
if (!this.active) {
return this.fn()
}
let parent: ReactiveEffect | undefined = activeEffect
let lastShouldTrack = shouldTrack
//循环遍历parent的父级元素
//直到找到了与当前实例this相同的父级元素,或者直到parent为null为止
//避免在组件的父子关系中出现循环引用的情况
while (parent) {
if (parent === this) {
return
}
parent = parent.parent
}
try {
//将上一个activeEffect先保存起来
this.parent = activeEffect
// 将自己设置为当前活跃的 ReactiveEffect
activeEffect = this
shouldTrack = true
//标记effect层级,执行一层effect,++effectTrackDepth
trackOpBit = 1 << ++effectTrackDepth
// 超过 maxMarkerBits 则 trackOpBit 的计算会超过最大整形的位数,降级为 cleanupEffect
if (effectTrackDepth <= maxMarkerBits) {
//给收集到的dpes数组打标记
initDepMarkers(this)
} else {
//重置deps数组依赖,需要重新收集
cleanupEffect(this)
}
// 执行fn
return this.fn()
} finally {
if (effectTrackDepth <= maxMarkerBits) {
// 执行完effect,看一下需要删除那些依赖添加哪些依赖
finalizeDepMarkers(this)
}
//标记effect层级,执行完--effectTrackDepth
trackOpBit = 1 << --effectTrackDepth
activeEffect = this.parent
shouldTrack = lastShouldTrack
this.parent = undefined
if (this.deferStop) {
this.stop()
}
}
}
stop() {
// stopped while running itself - defer the cleanup
if (activeEffect === this) {
this.deferStop = true
} else if (this.active) {
cleanupEffect(this)
if (this.onStop) {
this.onStop()
}
this.active = false
}
}
}
1.2 componentUpdateFn
componentUpdateFn 是根据isMounted进行不同的逻辑处理,如果isMounted为true则进行更新处理,isMounted为false则进行初始化处理 (先说初始化)
初始化处理主要 1.先检查有无beforeMount,有则触发 2. 调用renderComponentRoot函数并把1返回值赋值给instance.subTree和subTree (renderComponentRoot的主要作用就是调用render方法,并且通过call将this绑定在instance.proxy)----注意:因为render函数会使用reactive,ref的值,因此会触发track,所以此时就是在进行依赖收集 ,最后会将render函数的值进行返回--(例如 h('div',{},this.msg))
3.此时subTree 类似 h('div',{},'Hello Vue'),为了防止内部还要组件,因此要基于subTree递归调用patch函数
因此 componentUpdateFn 内部会执行一个render函数,所以我们可以知道每一个组件其实就是一个reativeEffect
但是reative,ref内部是这么样,为什么就可以进行依赖收集,我们还不得而知,下一篇我们需要知道reative,ref内部如何进行操做 ?????
const componentUpdateFn = () => {
if (!instance.isMounted) {
// 此时activeEffect为当前的effect
// 组件初始化的时候会执行这里
// 为什么要在这里调用 render 函数呢
// 是因为在 effect 内调用 render 才能触发依赖收集
// 等到后面响应式的值变更后会再次触发这个函数
let vnodeHook: VNodeHook | null | undefined
const { el, props } = initialVNode
const { bm, m, parent } = instance
const isAsyncWrapperVNode = isAsyncWrapper(initialVNode)
toggleRecurse(instance, false)
// beforeMount hook
if (bm) {
invokeArrayFns(bm)
}
// onVnodeBeforeMount
if (
!isAsyncWrapperVNode &&
(vnodeHook = props && props.onVnodeBeforeMount)
) {
invokeVNodeHook(vnodeHook, parent, initialVNode)
}
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
instance.emit('hook:beforeMount')
}
toggleRecurse(instance, true)
if (el && hydrateNode) {
// vnode has adopted host node - perform hydration instead of mount.
const hydrateSubTree = () => {
if (__DEV__) {
startMeasure(instance, `render`)
}
//根渲染节点赋值到subTree---里面执行render函数(进行依赖的收集)
instance.subTree = renderComponentRoot(instance)
if (__DEV__) {
endMeasure(instance, `render`)
}
if (__DEV__) {
startMeasure(instance, `hydrate`)
}
hydrateNode!(
el as Node,
instance.subTree,
instance,
parentSuspense,
null
)
if (__DEV__) {
endMeasure(instance, `hydrate`)
}
}
if (isAsyncWrapperVNode) {
; (initialVNode.type as ComponentOptions).__asyncLoader!().then(
// note: we are moving the render call into an async callback,
// which means it won't track dependencies - but it's ok because
// a server-rendered async wrapper is already in resolved state
// and it will never need to change.
() => !instance.isUnmounted && hydrateSubTree()
)
} else {
hydrateSubTree()
}
} else {
if (__DEV__) {
startMeasure(instance, `render`)
}
//根渲染节点赋值到subTree---里面执行render函数
//1.执行render函数,创建虚拟子节点
//2.进行数据的依赖收集(setup中的数据进行了proxy代理)
//3.在render中通过this访问instance.proxy,instance.proxy对数据访问进行不同proxy处理
const subTree = (instance.subTree = renderComponentRoot(instance))
if (__DEV__) {
endMeasure(instance, `render`)
}
if (__DEV__) {
startMeasure(instance, `patch`)
}
// 基于 subTree 再次调用 patch
// 基于 render 返回的 vnode ,再次进行渲染
// 递归的开箱(对于render的虚拟节点进行patch)
patch(
null,
subTree,
container,
anchor,
instance,
parentSuspense,
isSVG
)
if (__DEV__) {
endMeasure(instance, `patch`)
}
// 把 root element 赋值给 组件的vnode.el ,为后续调用 $el 的时候获取值
// 是为了将组件和组件内部的根节点联系起来,以便正确地进行组件的初始化和挂载。
initialVNode.el = subTree.el
}
// mounted hook
if (m) { //mount放在post类型cheduler进行异步处理
queuePostRenderEffect(m, parentSuspense)
}
// onVnodeMounted
if (
!isAsyncWrapperVNode &&
(vnodeHook = props && props.onVnodeMounted)
) {
const scopedInitialVNode = initialVNode
queuePostRenderEffect(
() => invokeVNodeHook(vnodeHook!, parent, scopedInitialVNode),
parentSuspense
)
}
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
queuePostRenderEffect(
() => instance.emit('hook:mounted'),
parentSuspense
)
}
// activated hook for keep-alive roots.
// #1742 activated hook must be accessed after first render
// since the hook may be injected by a child keep-alive
if (
initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE ||
(parent &&
isAsyncWrapper(parent.vnode) &&
parent.vnode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE)
) {
instance.a && queuePostRenderEffect(instance.a, parentSuspense)
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
queuePostRenderEffect(
() => instance.emit('hook:activated'),
parentSuspense
)
}
}
instance.isMounted = true
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
devtoolsComponentAdded(instance)
}
// #2458: deference mount-only object parameters to prevent memleaks
initialVNode = container = anchor = null as any
} else {
//更新组件逻辑
// updateComponent
// This is triggered by mutation of component's own state (next: null)
// OR parent calling processComponent (next: VNode)
let { next, bu, u, parent, vnode } = instance
let originNext = next
let vnodeHook: VNodeHook | null | undefined
if (__DEV__) {
pushWarningContext(next || instance.vnode)
}
// Disallow component effect recursion during pre-lifecycle hooks.
toggleRecurse(instance, false)
// 如果有 next 的话, 说明需要更新组件的数据(props,slots 等)
// 先更新组件的数据,然后更新完成后,在继续对比当前组件的子元素
if (next) {
//父组件更新调用processComponet触发子组件更新
//新组件 next 的 el 属性指向老组件 vnode 的 el 属性
// 让新组件直接使用旧组件的 el 对象
//instance.next = n2----updateComponent函数
//说明next只是虚拟节点,next此时并没有el属性
next.el = vnode.el
updateComponentPreRender(instance, next, optimized)
} else {
//组件自身的状态变化,触发组件更新
next = vnode
}
// beforeUpdate hook
if (bu) {
invokeArrayFns(bu)
}
// onVnodeBeforeUpdate
if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) {
invokeVNodeHook(vnodeHook, parent, next, vnode)
}
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
instance.emit('hook:beforeUpdate')
}
toggleRecurse(instance, true)
// render
if (__DEV__) {
startMeasure(instance, `render`)
}
// 调用render函数--重新进行依赖收集和节点创建并赋值给nextTree
const nextTree = renderComponentRoot(instance)
if (__DEV__) {
endMeasure(instance, `render`)
}
const prevTree = instance.subTree
instance.subTree = nextTree
if (__DEV__) {
startMeasure(instance, `patch`)
}
//patch prevTree和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) { //updated在post类型scheduler进行异步处理
queuePostRenderEffect(u, parentSuspense)
}
// onVnodeUpdated
if ((vnodeHook = next.props && next.props.onVnodeUpdated)) {
queuePostRenderEffect(
() => invokeVNodeHook(vnodeHook!, parent, next!, vnode),
parentSuspense
)
}
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
queuePostRenderEffect(
() => instance.emit('hook:updated'),
parentSuspense
)
}
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
devtoolsComponentUpdated(instance)
}
if (__DEV__) {
popWarningContext()
}
}
}