目录
- 【真香系列】Vue-Next 源码第一章:阅读源码的准备工作
- 【真香系列】Vue-Next 源码第二章:初始化流程上
- 【真香系列】Vue-Next 源码第三章:初始化流程下
- 【真香系列】Vue-Next 源码第四章:更新
- 【真香系列】Vue-Next 源码第五章:reative & effect
- 【真香系列】Vue-Next 源码第六章:新特性原理
- 【真香系列】Vue-Next 源码第七章:实战组件
setupRenderEffect
上文提到 setupComponent 之后,会调用 setupRenderEffect 生成一个渲染的副作用方法。componentEffect 就是更新时会执行的方法,这里会执行组件的生命周期方法等,renderComponentRoot 会执行之前编译出来的 code 以生成 vnode 并且 normalizeVNode 会将 shapeFlag 变为 9,然后进行 processElement 操作。
// packages/runtime-core/src/renderer.ts
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
) => {
// create reactive effect for rendering
instance.update = effect(function componentEffect() {
if (!instance.isMounted) {
let vnodeHook: VNodeHook | null | undefined
const { el, props } = initialVNode
const { bm, m, parent } = instance
// beforeMount hook
if (bm) {
invokeArrayFns(bm)
}
// onVnodeBeforeMount
if ((vnodeHook = props && props.onVnodeBeforeMount)) {
invokeVNodeHook(vnodeHook, parent, initialVNode)
}
const subTree = (instance.subTree = renderComponentRoot(instance))
if (el && hydrateNode) {
} else {
patch(
null,
subTree,
container,
anchor,
instance,
parentSuspense,
isSVG
)
initialVNode.el = subTree.el
}
// mounted hook
if (m) {
queuePostRenderEffect(m, parentSuspense)
}
// onVnodeMounted
if ((vnodeHook = props && props.onVnodeMounted)) {
queuePostRenderEffect(() => {
invokeVNodeHook(vnodeHook!, parent, initialVNode)
}, 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
const { a } = instance
if (
a &&
initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
) {
queuePostRenderEffect(a, parentSuspense)
}
instance.isMounted = true
} else {
// updateComponent
}
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
}
effect方法保证了在每次更新时一定会执行componentEffect,后面会详细说明,先继续看主要流程。
patch
// packages/runtime-core/src/renderer.ts
const patch: PatchFn = (
n1,
n2,
container,
anchor = null,
parentComponent = null,
parentSuspense = null,
isSVG = false,
optimized = false
) => {
const { type, ref, shapeFlag } = n2
switch (type) {
case Text:
case Comment:
case Static:
case Fragment:
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
}
}
processElement
初始化会执行 mountElement。
// packages/runtime-core/src/renderer.ts
const processElement = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
optimized: boolean
) => {
isSVG = isSVG || (n2.type as string) === 'svg'
if (n1 == null) {
mountElement(
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else {
patchElement(n1, n2, parentComponent, parentSuspense, isSVG, optimized)
}
}
mountElement
// packages/runtime-core/src/renderer.ts
const mountElement = (
vnode: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
optimized: boolean
) => {
let el: RendererElement
let vnodeHook: VNodeHook | undefined | null
const {
type,
props,
shapeFlag,
transition,
scopeId,
patchFlag,
dirs
} = vnode
...
hostInsert(el, container, anchor)
}
还记得第一篇文章提到过的通过 createRenderer 创建平台相关的渲染器吗?参数 rendererOptions 包含了相关的 dom 操作,hostInsert 就是插入 dom 方法。该操作执行完后,组件完成挂载,渲染完成。
// packages/runtime-dom/src/nodeOps.ts
insert: (child, parent, anchor) => {
parent.insertBefore(child, anchor || null)
}
effect
现在回头看 effect 方法,调用 createReactiveEffect 产生一个基于 componentEffect 的 reactiveEffect 方法,首次加载页面会立即执行一次。
// packages/reactivity/src/effect.ts
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
}
createReactiveEffect
首先 effect 会先入栈,然后执行 fn(componentEffect) 时,执行的代码中会触发响应数据 get, get 会使用 track 进行依赖收集,后文会详细说明,当执行完 fn 最后会出栈。
function createReactiveEffect<T = any>(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect<T> {
const effect = function reactiveEffect(): unknown {
if (!effect.active) {
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]
}
}
} as ReactiveEffect
effect.id = uid++
effect._isEffect = true
effect.active = true
effect.raw = fn
effect.deps = []
effect.options = options
return effect
}
总结
至此初始化流程已经全部完毕,其中较为重要的部分是通过 effect 进行了依赖收集,下一篇文章会介绍更新流程。