上一篇分析了 createApp 创建app经过了哪些过程
接下来我们看看setup 方法是如何被执行的, 当mount方法被调用时,组件开始渲染
createApp().mount('#app')
也就会进入 app.mount 内部, 调用 mount
方法
export const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args)
const { mount } = app // 取出原本的 mount
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
const container = normalizeContainer(containerOrSelector)
if (!container) return
const component = app._component // 取出组件的对象数据
if (!isFunction(component) && !component.render && !component.template) {
component.template = container.innerHTML
}
container.innerHTML = ''
const proxy = mount(container, false, container instanceof SVGElement)
if (container instanceof Element) {
container.removeAttribute('v-cloak')
container.setAttribute('data-v-app', '')
}
return proxy
}
return app
}) as CreateAppFunction<Element>
我们进入 mount 方法内部, 在其内部首先会判断 isMounted , 根据这个变量判断是否被挂载过, 发现从来没挂载过, 这时候开始创建 vnode, rootComponent 参数就是vue组件的对象信息, rootProps 看变量名也知道了是props
import { createVNode } from './vnode'
mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {
if (!isMounted) {
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
vnode.appContext = context
render(vnode, rootContainer, isSVG)
isMounted = true
app._container = rootContainer
;(rootContainer as any).__vue_app__ = app
return vnode.component!.proxy
}
}
进入 createVNode 方法,发现实际调用的是 _createVNode, 进入函数首先判断是节点类型,若类型不存在或是非动态组件,将type改写为 Comment 类型
然后判断是否为 vnode , 判断依据是是否存在 __v_isVNode
属性 ,这是本身就是vnode的情况, 那就会克隆一份再返回;
接着判断是否是类组件, 依据的是 __vccOpts
属性, 如是那会修改type变量;
然后判断 props, 如果有的话会对props 进行标准化处理, 特别是对props其中的 style
接下来的 shapeFlag 变量就是判断出 vnode的类型
最后再调用 createBaseVNode 方法,在这个方法内会真正的去创建一个 vnode
export const createVNode = (
_createVNode
) as typeof _createVNode
export function isVNode(value: any): value is VNode {
return value ? value.__v_isVNode === true : false
}
export function isClassComponent(value: unknown): value is ClassComponent {
return isFunction(value) && '__vccOpts' in value
}
function _createVNode(
type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, // 节点类型, 或者是组件对象
props: (Data & VNodeProps) | null = null, // 节点props
children: unknown = null, // sub Stree
patchFlag: number = 0, // 补丁标记, 为了优化
dynamicProps: string[] | null = null,
isBlockNode = false
): VNode {
if (!type || type === NULL_DYNAMIC_COMPONENT) {
type = Comment
}
if (isVNode(type)) { // 本身就是 vNode的情况
// createVNode receiving an existing vnode. This happens in cases like
// <component :is="vnode"/>
// #2078 make sure to merge refs during the clone instead of overwriting it
const cloned = cloneVNode(type, props, true /* mergeRef: true */) // 克隆一份 vode
if (children) { // 再处理 vnode 的子元素
normalizeChildren(cloned, children)
}
return cloned
}
// class component normalization.
if (isClassComponent(type)) { // 类 组件
type = type.__vccOpts
}
// class & style normalization.
if (props) {
// for reactive or proxy objects, we need to clone it to enable mutation.
props = guardReactiveProps(props)!
let { class: klass, style } = props
if (klass && !isString(klass)) {
props.class = normalizeClass(klass)
}
if (isObject(style)) {
// reactive state objects need to be cloned since they are likely to be
// mutated
if (isProxy(style) && !isArray(style)) {
style = extend({}, style)
}
props.style = normalizeStyle(style)
}
}
// encode the vnode type information into a bitmap // 用来标记VNode种类的标志位
const shapeFlag = isString(type) // 如果是 string 那就是 element 类型
? ShapeFlags.ELEMENT // 如果不是 string 判断是否是 suspense 组件 如果是 suspense就 标记 ShapeFlags.SUSPENSE
: ( __FEATURE_SUSPENSE__ && isSuspense(type) // -如果不是 suspense , 那就判断是否是传送门组件
? ShapeFlags.SUSPENSE // 如果是传送门组件 ,给标记 ShapeFlags.TELEPORT, 如果不是
: ( isTeleport(type) // 判断是 type 否是 object, 那就是有状态组件
? ShapeFlags.TELEPORT // 如果 type 不是 object , 继续判断 是否是函数
: ( isObject(type) // 如果 tyoe 是 function , 那就是 functional 组件
? ShapeFlags.STATEFUL_COMPONENT // 如果都不是,就标记为 0 咯
: ( isFunction(type)
? ShapeFlags.FUNCTIONAL_COMPONENT
: 0 ))))
return createBaseVNode(
type,
props,
children,
patchFlag,
dynamicProps,
shapeFlag,
isBlockNode,
true
)
}
首先长长的定义了一个 vnode 变量, 有我们常见的vnode上的各种属性,还有 __v_isVNode
这样用于内部判断的私有属性
再通过 needFullChildrenNormalization 参数判断是否需要标准化 children , 因为在上面的代码里我们看到调用 createBaseVNode 时就是传入的true, normalizeChildren 内部呢就是判断children 类型然后对vnode的 shapeFlag 进行附加, 这里值得一提的是, vue内部大量使用了位运算,举个例子, 比如定义三个类型 a, b, c, a = 1
, b = 1 << 1
(也就是二进制 10),c = 1 << 2
(二进制 100) 这时候如果一开始 type = c , 但是我想让 type变量同时存储 a 类型和 c 类型,正常情况下,我们会这么做: type = [a, c]
对吧, 这样可以,但是后续的类型判断很麻烦, 也很慢, 必须遍历 type 数组, 位运算的好处呢, 就是可以解决这样的问题, 如果想让 type 增加 a类型, 我只需要type |= a
, 这样 type 就会从一开始的二进制 100 变为 101, 然后在判断类型的时候就可以使用 type & a
快速的进行判断得出结果,这里 normalizeChildren 函数内 vnode.shapeFlag |= type
就是如此,进行类型的增加
最后再返回这个 vnode
function createBaseVNode(
type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
props: (Data & VNodeProps) | null = null,
children: unknown = null,
patchFlag = 0,
dynamicProps: string[] | null = null,
shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,
isBlockNode = false,
needFullChildrenNormalization = false
) {
const vnode = {
__v_isVNode: true,
__v_skip: true,
type,
props,
key: props && normalizeKey(props),
ref: props && normalizeRef(props),
scopeId: currentScopeId,
slotScopeIds: null,
children,
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
} as VNode
if (needFullChildrenNormalization) { // 需要标准化 children
normalizeChildren(vnode, children)
// normalize suspense children
if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { // 判断 SUSPENSE 组件
;(type as typeof SuspenseImpl).normalize(vnode)
}
} else if (children) {
// compiled element vnode - if children is passed, only possible types are
// string or Array.
vnode.shapeFlag |= isString(children) // 文本节点或者 子组件数组
? ShapeFlags.TEXT_CHILDREN
: ShapeFlags.ARRAY_CHILDREN
}
// track vnode for block tree
if (
isBlockTreeEnabled > 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.
(vnode.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.
vnode.patchFlag !== PatchFlags.HYDRATE_EVENTS
) {
currentBlock.push(vnode) // 当前依赖收集 block 加入这个 vnode ,然后这些存在 patch flag 标记的 vnode 会被追加到 currentBlock 中收集
}
return vnode
}
export function normalizeChildren(vnode: VNode, children: unknown) {
let type = 0
const { shapeFlag } = vnode // 取出节点类型
if (children == null) {
children = null
} else if (isArray(children)) { // 如果本身就是数组, 那就是 arrary_children type
type = ShapeFlags.ARRAY_CHILDREN
} else if (typeof children === 'object') { // 子组件是对象
if (shapeFlag & (ShapeFlags.ELEMENT | ShapeFlags.TELEPORT)) { // shapeFlag 是 ELEMENT 或者 TELEPORT. 确实判断会方便很多
// Normalize slot to plain children for plain element and Teleport 为普通元素和传送将插槽规范化为普通子元素
const slot = (children as any).default // 取出 default
if (slot) {
// _c marker is added by withCtx() indicating this is a compiled slot _c标记是由withCtx()添加的,表示这是一个编译后的槽位
slot._c && (slot._d = false)
normalizeChildren(vnode, slot())
slot._c && (slot._d = true)
}
return
} else {
type = ShapeFlags.SLOTS_CHILDREN // 插槽子元素
const slotFlag = (children as RawSlots)._
if (!slotFlag && !(InternalObjectKey in children!)) {
// if slots are not normalized, attach context instance
// (compiled / normalized slots already have context)
;(children as RawSlots)._ctx = currentRenderingInstance
} else if (slotFlag === SlotFlags.FORWARDED && currentRenderingInstance) {
// a child component receives forwarded slots from the parent.
// its slot type is determined by its parent's slot type.
if (
(currentRenderingInstance.slots as RawSlots)._ === SlotFlags.STABLE
) {
;(children as RawSlots)._ = SlotFlags.STABLE
} else {
;(children as RawSlots)._ = SlotFlags.DYNAMIC
vnode.patchFlag |= PatchFlags.DYNAMIC_SLOTS
}
}
}
} else if (isFunction(children)) { // 函数子组件,构造 default
children = { default: children, _ctx: currentRenderingInstance }
type = ShapeFlags.SLOTS_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
}
}
vnode.children = children as VNodeNormalizedChildren
vnode.shapeFlag |= type // vnode.shapeFlag = vnode.shapeFlag | type
}
此时又回到了mount 方法, 挂载上 context 上下文, 这里的 context 是在 createApp 调用时就被创建好的。 接着就调用render 方法,这个render 方法还记得吗是在创建 renderer 的时候就被创建好,保存在闭包里的, 我们接着看看
import { createVNode } from './vnode'
mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {
if (!isMounted) {
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
vnode.appContext = context
render(vnode, rootContainer, isSVG)
isMounted = true
app._container = rootContainer
;(rootContainer as any).__vue_app__ = app
return vnode.component!.proxy
}
}
render 方法比较简单,接收 vnode 和 dom对象, 如果vode 为null它就认为是需要卸载组件, 否则就会进入patch 函数, 并且将 vnode 挂到 dom 的 _vnode
属性上
const render: RootRenderFunction = (vnode, container, isSVG) => {
if (vnode == null) { // 新 vnode 不存在
if (container._vnode) { // 有旧 vnode
unmount(container._vnode, null, null, true) // 执行现在 vnode
}
} else {
patch(container._vnode || null, vnode, container, null, null, null, isSVG) // 进行patch 打补丁, 第一次进入还没挂载过所以 container._vnode 就是空的
}
flushPostFlushCbs()
container._vnode = vnode // 当前vNode 继续挂到 dom 上
}
patch 具体如下, 会判断 n1 n2 两个 vnode 引用是否相同、属性是否相同、类型是否相同等等 然后根据 type 类型进入不同的实际的处理流程,这里我们传入的是 组件节点, 因此会调用 processElement 方法
const patch: PatchFn = (
n1,
n2,
container,
anchor = null,
parentComponent = null,
parentSuspense = null,
isSVG = false,
slotScopeIds = null,
optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren
) => {
if (n1 === n2) { // n1 n2 两个 vNode 完全相等的情况, 那就不用 patch 了
return
}
// patching & not same type, unmount old tree
if (n1 && !isSameVNodeType(n1, n2)) { // 当n1 已存在,代表是更新操作, 并且 n1.type === n2.type && n1.key === n2.key 粗浅判断
anchor = getNextHostNode(n1)
unmount(n1, parentComponent, parentSuspense, true)
n1 = null
}
if (n2.patchFlag === PatchFlags.BAIL) {
optimized = false
n2.dynamicChildren = null
}
const { type, ref, shapeFlag } = n2
switch (type) { // 组件类型判断
case Text:
processText(n1, n2, container, anchor)
break
case Comment:
processCommentNode(n1, n2, container, anchor)
break
case Static:
if (n1 == null) {
mountStaticNode(n2, container, anchor, isSVG)
} else if (__DEV__) {
patchStaticNode(n1, n2, container, isSVG)
}
break
case Fragment:
processFragment(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
break
default:
if (shapeFlag & ShapeFlags.ELEMENT) { // 是 普通元素
processElement(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else if (shapeFlag & ShapeFlags.COMPONENT) { //组件节点
processComponent(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else if (shapeFlag & ShapeFlags.TELEPORT) { // 传送组件
;(type as typeof TeleportImpl).process(
n1 as TeleportVNode,
n2 as TeleportVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized,
internals
)
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { // 悬念组件
;(type as typeof SuspenseImpl).process(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized,
internals
)
}
}
// set ref
if (ref != null && parentComponent) {
setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2)
}
}
processComponent 同样的也会根据 n1 是否存在判断是挂载组件还是更新组件,并且内部还有对 keep-alive 的处理, 因为我们是第一次挂在,所以又会进入 mountComponent 方法 (这调用层级也太多了吧😭)
const processComponent = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean
) => {
n2.slotScopeIds = slotScopeIds
if (n1 == null) { // 挂载操作
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) { // keep-alive 组件
;(parentComponent!.ctx as KeepAliveContext).activate(
n2,
container,
anchor,
isSVG,
optimized
)
} else { // 否则正常挂载元素
mountComponent(
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
} else { // 更新操作
updateComponent(n1, n2, optimized)
}
}
mountComponent 方法内, 先是取出 componentInstance, 如果没有就创建一个, 然后是针对 keep-alive 进行注入针对这个组件特殊实现的 renderer 。 还有对 suspense 组件的特殊处理
重要的是过程中调用了 setupComponent 并传入了 instance
const mountComponent: MountComponentFn = (
initialVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
// 2.x compat may pre-creaate the component instance before actually 2.X compat可以在实际创建之前预先创建组件实例
// mounting
const compatMountInstance =
__COMPAT__ && initialVNode.isCompatRoot && initialVNode.component
const instance: ComponentInternalInstance = // 创建组件 实例咯
compatMountInstance ||
(initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
// inject renderer internals for keepAlive
if (isKeepAlive(initialVNode)) {
;(instance.ctx as KeepAliveContext).renderer = internals
}
// resolve props and slots for setup context
if (!(__COMPAT__ && compatMountInstance)) {
setupComponent(instance)
}
// setup() is async. This component relies on async logic to be resolved
// before proceeding
if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect)
// Give it a placeholder if this is not hydration
// TODO handle self-defined fallback
if (!initialVNode.el) {
const placeholder = (instance.subTree = createVNode(Comment))
processCommentNode(null, placeholder, container!, anchor)
}
return
}
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
)
}
setupComponent 内部对 props 和 slots 属性都进行了初始化, 并判断了是否为 stateful组件, 如果是那还会调用 setupStatefulComponent, 其内部呢就是取出了 instance.type 上的 setup 方法 通过 callWithErrorHandling 函数进行了执行,这个函数是为了更好的处理异常,setup返回结果被存储在了 setupResult 变量, 并且可以看到还有针对 setupResult 为异步promise 的判断,并针对其的处理
export function setupComponent(
instance: ComponentInternalInstance,
isSSR = false
) {
isInSSRComponentSetup = isSSR
const { props, children } = instance.vnode
const isStateful = isStatefulComponent(instance)
initProps(instance, props, isStateful, isSSR)
initSlots(instance, children) // 插槽初始化
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)
: undefined
isInSSRComponentSetup = false
return setupResult
}
function setupStatefulComponent(
instance: ComponentInternalInstance,
isSSR: boolean
) {
const Component = instance.type as ComponentOptions
// 0. create render proxy property access cache
instance.accessCache = Object.create(null) // 访问缓存
// 1. create public instance / render proxy
// also mark it raw so it's never observed
instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers)) //
// 2. call setup()
const { setup } = Component
if (setup) {
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null) // 秒啊, 用 function length 判断是否使用 context, 不使用就不创建context
setCurrentInstance(instance) // 将当前 instance 挂至全局变量
pauseTracking() // 暂停追踪 至于内部实现有待研究
const setupResult = callWithErrorHandling(
setup,
instance,
ErrorCodes.SETUP_FUNCTION, // 传入这个type 也只是用在setup有异常的时候
[instance.props, setupContext]
)
resetTracking()
unsetCurrentInstance()
if (isPromise(setupResult)) { // 针对 setup 返回的是 promise 进行处理
setupResult.then(unsetCurrentInstance, unsetCurrentInstance) // 解决异步使用setup的时候,
} else {
handleSetupResult(instance, setupResult, isSSR)
}
} else {
finishComponentSetup(instance, isSSR)
}
}
如果非异步的 setup返回结果会进入到 handleSetupResult 方法, 如果返回的是对象, 那就会先被 proxy 包转成 ref , 然后挂到 instance.setupState 属性上
export function handleSetupResult( // 处理 setup函数的结果
instance: ComponentInternalInstance,
setupResult: unknown,
isSSR: boolean
) {
if (isFunction(setupResult)) { // setup 可以返回一个渲染函数
// setup returned an inline render function
if (__NODE_JS__ && (instance.type as ComponentOptions).__ssrInlineRender) { // ssr 状态
// when the function's name is `ssrRender` (compiled by SFC inline mode),
// set it as ssrRender instead.
instance.ssrRender = setupResult
} else {
instance.render = setupResult as InternalRenderFunction // render function 赋值
}
} else if (isObject(setupResult)) { // 如果是正常的对象对象返回
// setup returned bindings.
// assuming a render function compiled from template is present.
instance.setupState = proxyRefs(setupResult) // 把结果包装成ref 挂在 setupState 上
}
finishComponentSetup(instance, isSSR)
}
ok 到这里总算是看到 setup 执行了,
我们来梳理下调用过程:
app.mount -> mount -> render -> _createVNode -> createBaseVNode -> render -> patch -> processComponent -> mountComponent -> setupComponent -> setupStatefulComponent -> handleSetupResult -> finishComponentSetup