目录
- 【真香系列】Vue-Next 源码第一章:阅读源码的准备工作
- 【真香系列】Vue-Next 源码第二章:初始化流程上
- 【真香系列】Vue-Next 源码第三章:初始化流程下
- 【真香系列】Vue-Next 源码第四章:更新
- 【真香系列】Vue-Next 源码第五章:reative & effect
- 【真香系列】Vue-Next 源码第六章:新特性原理
- 【真香系列】Vue-Next 源码第七章:实战组件
mount
mount
方法首先创建了 vnode
,然后渲染 vnode
,最后返回组件的代理对象。
// packages/runtime-core/src/apiCreateApp.ts
mount(rootContainer: HostElement, isHydrate?: boolean): any {
if (!isMounted) {
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
// store app context on the root VNode.
// this will be set on the root instance on initial mount.
vnode.appContext = context
render(vnode, rootContainer)
isMounted = true
app._container = rootContainer
// for devtools and telemetry
;(rootContainer as any).__vue_app__ = app
return vnode.component!.proxy
}
}
createVNode
// packages/runtime-core/src/vnode.ts
function _createVNode(
type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
props: (Data & VNodeProps) | null = null,
children: unknown = null,
patchFlag: number = 0,
dynamicProps: string[] | null = null,
isBlockNode = false
): VNode {
// class component normalization.
if (isClassComponent(type)) {}
// class & style normalization.
if (props) {}
// encode the vnode type information into a bitmap
const shapeFlag = isString(type)
? ShapeFlags.ELEMENT
: __FEATURE_SUSPENSE__ && isSuspense(type)
? ShapeFlags.SUSPENSE
: isTeleport(type)
? ShapeFlags.TELEPORT
: isObject(type)
? ShapeFlags.STATEFUL_COMPONENT
: isFunction(type)
? ShapeFlags.FUNCTIONAL_COMPONENT
: 0
const vnode: VNode = {
__v_isVNode: true,
[ReactiveFlags.SKIP]: true,
type,
props,
key: props && normalizeKey(props),
ref: props && normalizeRef(props),
scopeId: currentScopeId,
children: null,
component: null,
suspense: null,
ssContent: null,
ssFallback: null,
dirs: null,
transition: null,
el: null,
anchor: null,
target: null,
targetAnchor: null,
staticCount: 0,
shapeFlag,
patchFlag,
dynamicProps,
dynamicChildren: null,
appContext: null
}
normalizeChildren(vnode, children)
// normalize suspense children
if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
const { content, fallback } = normalizeSuspenseChildren(vnode)
vnode.ssContent = content
vnode.ssFallback = fallback
}
if (
shouldTrack > 0 &&
// avoid a block node from tracking itself
!isBlockNode &&
// has current parent block
currentBlock &&
// presence of a patch flag indicates this node needs patching on updates.
// component nodes also should always be patched, because even if the
// component doesn't need to update, it needs to persist the instance on to
// the next vnode so that it can be properly unmounted later.
(patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
// the EVENTS flag is only for hydration and if it is the only flag, the
// vnode should not be considered dynamic due to handler caching.
patchFlag !== PatchFlags.HYDRATE_EVENTS
) {
currentBlock.push(vnode)
}
return vnode
}
参数 type
是例子中的组件选项 {template: ..., setup() {...}}
,这里首次出现了 shapeFlag
,意为 vnode
的类型,每种类型都有对应的处理方法,在 Vue3
中将 vnode
大致分为一下几类:
// packages/shared/src/shapeFlags.ts
export const enum ShapeFlags {
ELEMENT = 1, // 元素 string
FUNCTIONAL_COMPONENT = 1 << 1, // 2 function
STATEFUL_COMPONENT = 1 << 2, // 4 object
TEXT_CHILDREN = 1 << 3, // 8 文本
ARRAY_CHILDREN = 1 << 4, // 16 数组
SLOTS_CHILDREN = 1 << 5, // 32 插槽
TELEPORT = 1 << 6, // 64 teleport
SUSPENSE = 1 << 7, // 128 suspense
COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,// 256 keep alive 组件
COMPONENT_KEPT_ALIVE = 1 << 9, // 512 keep alive 组件
COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT // 组件
}
这里选项是对象,所以 shapeFlag
为 4,_createVNode
中额外要提及的是 currentBlock
和 patchFlag
,这两个跟新特性相关,后面的文章中会详细说明。
render
创建完 vnode
之后,调用实例上的 render
方法,进行第一次 patch
过程,最后 flushPostFlushCbs
执行了一次清空任务调度队列,任务调度会在后面详细说。
// packages/runtime-core/src/renderer.ts baseCreateRenderer
const render: RootRenderFunction = (vnode, container) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
patch(container._vnode || null, vnode, container)
}
flushPostFlushCbs()
container._vnode = vnode
}
patch
前面得知 shapeFlag
为 4,所以会调用 processComponent
进行处理。
// 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) {
} else if (shapeFlag & ShapeFlags.COMPONENT) {
processComponent(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else if (shapeFlag & ShapeFlags.TELEPORT) {
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
}
}
}
processComponent
这里因为是初始化,所以会直接调用 mountComponent
挂载组件。
// packages/runtime-core/src/renderer.ts
const processComponent = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
optimized: boolean
) => {
if (n1 == null) {
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
} else {
mountComponent(
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
} else {
updateComponent(n1, n2, optimized)
}
}
mountComponent
首先 createComponentInstance
创建了组件实例,然后调用 setupComponent
去执行 setup
函数获取返回值,最后执行 setupRenderEffect
去定义渲染相关的 effect
。setupComponent
和 setupRenderEffect
是初始化流程中相对比较重要的两个环节。
// packages/runtime-core/src/renderer.ts
const mountComponent: MountComponentFn = (
initialVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
setupComponent(instance)
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
)
}
setupComponent
初始化 props
、 attrs
和 slots
,前面提到 shapeFlag
为 STATEFUL_COMPONENT
,所以会通过 setupStatefulComponent
执行 setup
。
// packages/runtime-core/src/component.ts
export function setupComponent(
instance: ComponentInternalInstance,
isSSR = false
) {
isInSSRComponentSetup = isSSR
const { props, children, shapeFlag } = instance.vnode
const isStateful = shapeFlag & ShapeFlags.STATEFUL_COMPONENT
initProps(instance, props, isStateful, isSSR)
initSlots(instance, children)
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)
: undefined
isInSSRComponentSetup = false
return setupResult
}
setupStatefulComponent
callWithErrorHandling
执行了 setup
, 返回结果 setupResult
传入 handleSetupResult
进行 proxy
包装并将 template
编译。
// packages/runtime-core/src/component.ts
function setupStatefulComponent(
instance: ComponentInternalInstance,
isSSR: boolean
) {
const Component = instance.type as ComponentOptions
// 0. create render proxy property access cache
instance.accessCache = {}
// 1. create public instance / render proxy
// also mark it raw so it's never observed
instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
// 2. call setup()
const { setup } = Component
if (setup) {
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null)
currentInstance = instance
pauseTracking()
const setupResult = callWithErrorHandling(
setup,
instance,
ErrorCodes.SETUP_FUNCTION,
[__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
)
resetTracking()
currentInstance = null
if (isPromise(setupResult)) {
} else {
handleSetupResult(instance, setupResult, isSSR)
}
} else {
finishComponentSetup(instance, isSSR)
}
}
callWithErrorHandling
执行 setup
// packages/runtime-core/src/errorHandling.ts
export function callWithErrorHandling(
fn: Function,
instance: ComponentInternalInstance | null,
type: ErrorTypes,
args?: unknown[]
) {
let res
try {
res = args ? fn(...args) : fn()
} catch (err) {
handleError(err, instance, type)
}
return res
}
handleSetupResult
proxyRefs
对 setup
结果包装 Proxy
,然后挂载到 instance.setupState
。
// packages/runtime-core/src/component.ts
export function handleSetupResult(
instance: ComponentInternalInstance,
setupResult: unknown,
isSSR: boolean
) {
if (isFunction(setupResult)) {
// setup returned bindings.
// assuming a render function compiled from template is present.
instance.render = setupResult as InternalRenderFunction
} else if (isObject(setupResult)) {
instance.setupState = proxyRefs(setupResult)
}
finishComponentSetup(instance, isSSR)
}
finishComponentSetup
编译模版
// packages/runtime-core/src/component.ts
function finishComponentSetup(
instance: ComponentInternalInstance,
isSSR: boolean
) {
const Component = instance.type as ComponentOptions
// template / render function normalization
if (__NODE_JS__ && isSSR) {
} else if (!instance.render) {
// could be set from setup()
if (compile && Component.template && !Component.render) {
Component.render = compile(Component.template, {
isCustomElement: instance.appContext.config.isCustomElement,
delimiters: Component.delimiters
})
}
}
}
compile
ast
是基于模版生成的抽象语法树。
// packages/compiler-core/src/compile.ts
// we name it `baseCompile` so that higher order compilers like
// @vue/compiler-dom can export `compile` while re-exporting everything else.
export function baseCompile(
template: string | RootNode,
options: CompilerOptions = {}
): CodegenResult {
...
const ast = isString(template) ? baseParse(template, options) : template
const [nodeTransforms, directiveTransforms] = getBaseTransformPreset(
prefixIdentifiers
)
transform(
ast,
extend({}, options, {
prefixIdentifiers,
nodeTransforms: [
...nodeTransforms,
...(options.nodeTransforms || []) // user transforms
],
directiveTransforms: extend(
{},
directiveTransforms,
options.directiveTransforms || {} // user transforms
)
})
)
return generate(
ast,
extend({}, options, {
prefixIdentifiers
})
)
}
generate
生成了编译代码
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock("h1", null, _toDisplayString(state.message), 1 /* TEXT */))
}
}"
总结
初始化流程至此,主要分为:
- 创建了
vnode
节点 - 执行
setup
- 编译生成渲染函数
之后将会通过 setupRenderEffect
为渲染创建响应副作用 reactive effect
,也就是每次渲染时都会执行的函数。