分析mopuntConponent的三个方法
前一章我们分析到了mountCompoent方法,其内部调用了三个方法:
- createComponentInstance
- setupCompoent
- setupRenderEffect
现在来看看这三个方法
createComponentInstance
createComponentInstance方法 路径: core\packages\runtime-core\src\component.ts
export function createComponentInstance(
vnode: VNode,
parent: ComponentInternalInstance | null,
suspense: SuspenseBoundary | null
) {
const type = vnode.type as ConcreteComponent
// inherit parent app context - or - if root, adopt from root vnode
const appContext =
(parent ? parent.appContext : vnode.appContext) || emptyAppContext
const instance: ComponentInternalInstance = {
uid: uid++,
vnode,
type,
parent,
appContext,
root: null!, // to be immediately set
next: null,
subTree: null!, // will be set synchronously right after creation
effect: null!,
update: null!, // will be set synchronously right after creation
scope: new EffectScope(true /* detached */),
render: null,
proxy: null,
exposed: null,
exposeProxy: null,
withProxy: null,
provides: parent ? parent.provides : Object.create(appContext.provides),
accessCache: null!,
renderCache: [],
// local resovled assets
components: null,
directives: null,
// resolved props and emits options
propsOptions: normalizePropsOptions(type, appContext),
emitsOptions: normalizeEmitsOptions(type, appContext),
// emit
emit: null!, // to be set immediately
emitted: null,
// props default value
propsDefaults: EMPTY_OBJ,
// inheritAttrs
inheritAttrs: type.inheritAttrs,
// state
ctx: EMPTY_OBJ,
data: EMPTY_OBJ,
props: EMPTY_OBJ,
attrs: EMPTY_OBJ,
slots: EMPTY_OBJ,
refs: EMPTY_OBJ,
setupState: EMPTY_OBJ,
setupContext: null,
// suspense related
suspense,
suspenseId: suspense ? suspense.pendingId : 0,
asyncDep: null,
asyncResolved: false,
// lifecycle hooks
// not using enums here because it results in computed properties
isMounted: false,
isUnmounted: false,
isDeactivated: false,
bc: null,
c: null,
bm: null,
m: null,
bu: null,
u: null,
um: null,
bum: null,
da: null,
a: null,
rtg: null,
rtc: null,
ec: null,
sp: null
}
if (__DEV__) {
instance.ctx = createDevRenderContext(instance)
} else {
instance.ctx = { _: instance }
}
instance.root = parent ? parent.root : instance
instance.emit = emit.bind(null, instance)
// apply custom element special handling
if (vnode.ce) {
vnode.ce(instance)
}
return instance
}
createComponentInstance方法做了什么?
- 创建一个instance对象,上面有很多属性
- 返回instance对象
很明显能看出,这个方法就是创建一个instance实例,实例上有特别多的属性,最后返回了这个instance实例,这就是组件实例
但这里只是创建,所有属性都还没有值,之后会调用setupComponent方法去初始化组件实例
因此来看setupComponent方法的实现
这一步的注释: 创建组件实例
setupComponent
setupComponent方法 路径: core\packages\runtime-core\src\component.ts
export function setupComponent(
instance: ComponentInternalInstance,
isSSR = false
) {
isInSSRComponentSetup = isSSR
const { props, children } = instance.vnode
const isStateful = isStatefulComponent(instance)
//todo To: initProps
initProps(instance, props, isStateful, isSSR)
//todo To: initSlots
initSlots(instance, children)
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)
: undefined
isInSSRComponentSetup = false
return setupResult
}
setupComponent方法做了什么?
- 解构出新vnode的props和children属性
- 通过isStatefulComponent(instance)判断是否是有状态的组件(没有状态的组件是函数式组件)
- initProps初始化props,attrs(todo)
- initSlots初始化slots(todo)
- 根据isStateful变量决定是否执行setupStatefulComponent,因为组件初始化的时候,我们的组件不是函数式组件,肯定是有状态的,因此会执行setupStatefulComponent方法
- 返回setupResult
因为setupResult是setupStatefulComponent方法执行后的返回值,所以要看setupStatefulComponent方法实现
这一步的注释在:setupComponent
setupStatefulComponent
setupStatefulComponent方法 路径: core\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 = 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) {
//todo To: createSetupContext
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null)
setCurrentInstance(instance)
pauseTracking()
const setupResult = callWithErrorHandling(
setup,
instance,
ErrorCodes.SETUP_FUNCTION,
[__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
)
resetTracking()
unsetCurrentInstance()
if (isPromise(setupResult)) {
} else {
handleSetupResult(instance, setupResult, isSSR)
}
} else {
finishComponentSetup(instance, isSSR)
}
}
setupStatefulComponent方法做了什么?
- 解构出setup方法
- 判断setup是否存在
- 如果setup存在,根据setup参数的多少决定是否初始化instance.setupContext对象,中间调用了createSetupContext方法(todo)
- 调用setup方法,并把返回值给setupResult(这就是用户写的setup方法的返回值)
- 调用handleSetupResult方法对setupResult进行处理
- 如果Setup不存在,执行finishComponentSetup方法
这里主要就是调用了用户写的setup方法,将返回值赋值给setupResult
接着执行handleSetupResult方法处理setupResult,因此来看handleSetupResult方法的实现
这一步的注释在:setupStatefulComponent
handleSetupResult
handleSetupResult方法 路径: core\packages\runtime-core\src\component.ts
export function handleSetupResult(
instance: ComponentInternalInstance,
setupResult: unknown,
isSSR: boolean
) {
if (isFunction(setupResult)) {
// setup returned an inline render function
if (__SSR__ && (instance.type as ComponentOptions).__ssrInlineRender) {
// 忽略
} else {
instance.render = setupResult as InternalRenderFunction
}
} else if (isObject(setupResult)) {
// setup returned bindings.
// assuming a render function compiled from template is present.
instance.setupState = proxyRefs(setupResult)
} else if (__DEV__ && setupResult !== undefined) {
// 忽略
}
finishComponentSetup(instance, isSSR)
}
handleSetupResult方法做了什么?
- 判断setupResult是否是函数,如果是函数,那就是用户自己写的render函数了,instance.render = setupResult
- 判断setupResult是否是对象,如果是对象,就把这个对象赋值给instance.setupState
- 最后执行finishComponentSetup方法
根据setupResult的类型执行相对应的操作,如果是函数,代表的是用户自己编写的render函数,如果是对象,代表的是用户setup函数想要返回的对象
接着执行finishComponentSetup方法,接下来看finishComponentSetup方法的实现
这一步的注释在: handleSetupResult
finishComponentSetup
finishComponentSetup方法 路径: core\packages\runtime-core\src\component.ts
export function finishComponentSetup(
instance: ComponentInternalInstance,
isSSR: boolean,
skipOptions?: boolean
) {
const Component = instance.type as ComponentOptions
// template / render function normalization
// could be already set when returned from setup()
if (!instance.render) {
// only do on-the-fly compile if not in SSR - SSR on-the-fly compilation
// is done by server-renderer
if (!isSSR && compile && !Component.render) {
const template =
(__COMPAT__ &&
instance.vnode.props &&
instance.vnode.props['inline-template']) ||
Component.template
if (template) {
const { isCustomElement, compilerOptions } = instance.appContext.config
const { delimiters, compilerOptions: componentCompilerOptions } =
Component
const finalCompilerOptions: CompilerOptions = extend(
extend(
{
isCustomElement,
delimiters
},
compilerOptions
),
componentCompilerOptions
)
//todo To: compile
Component.render = compile(template, finalCompilerOptions)
}
}
instance.render = (Component.render || NOOP) as InternalRenderFunction
}
// support for 2.x options
if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {
setCurrentInstance(instance)
pauseTracking()
//todo To: applyOptions
applyOptions(instance)
resetTracking()
unsetCurrentInstance()
}
}
finishComponentSetup方法做了什么?
- 判断instance是否有render函数
- 如果没有render函数,经过一系列处理之后,通过compile方法(涉及编译todo)获得render函数赋值给instance.render
- applyOptions方法对vue2.0做一些兼容处理
判断当前实例是否有render函数(前一步在用户没有编写render函数时,此时render是不存在的),如果没有render函数,通过compile方法获得render函数,最后通过applyOptions对Vue2.0做兼容处理
这一步的注释在: finishComponentSetup
setupComponent执行完,做了什么?
到了这里,setupComponent的调用栈执行完毕,因此让我们一一返回
- finishComponentSetup: 生成render函数以及对vue2.0做兼容处理
- handleSetupResult: render,setupState以及对vue2.0做兼容处理返回到setupStatefulComponent
- setupStatefulComponent: 将render,setupState以及对vue2.0做兼容处理返回到setupComponent
- setupComponent: 将props,attrs,slots,render, setupstate,setupContext以及对vue2做了兼容处理返回到mountComponent
- 因此mountCompoent内部执行完setupComponent后: instance上多出了props,attrs,slots,render,setupState,setupContext(取决于setup的参数多少),以及对vue2做了兼容处理
这一步的注释在: 调用栈回到mountComponent
接下来回到mountComponent,继续执行setupRenderEffect方法,来看其实现
setupRenderEffect
setupRenderEffect方法 路径: core\packages\runtime-core\src\renderer.ts
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
) => {
const componentUpdateFn = () => {}
// create reactive effect for rendering
//todo To: new ReactiveEffect & queueJob
const effect = (instance.effect = new ReactiveEffect(
componentUpdateFn,
() => queueJob(instance.update),
instance.scope // track it in component's effect scope
))
//todo To: effect
const update = (instance.update = effect.run.bind(effect) as SchedulerJob)
update.id = instance.uid
// allowRecurse
// #1801, #2043 component render effects should allow recursive updates
//todo To: toggleRecurse
toggleRecurse(instance, true)
if (__DEV__) {
}
update()
}
setupRenderEffect方法做了什么?
- 创建更新函数 componentUpdateFn
- 创建更新机制 effect,new ReactiveEffect
- 更新视图,首次渲染 update()
这一步的注释在setupRenderEffect
现在来看 componentUpdateFn方法的实现
componentUpdateFn
componentUpdateFn方法 路径: core\packages\runtime-core\src\renderer.ts
const componentUpdateFn = () => {
// 判断mounted
if (!instance.isMounted) {
// 如果mounted为false
let vnodeHook: VNodeHook | null | undefined
// 获取el和props
const { el, props } = initialVNode
// 获取beforeMounted mounted钩子和parent
const { bm, m, parent } = instance
const isAsyncWrapperVNode = isAsyncWrapper(initialVNode)
//todo To: toggleRecurse
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) {
} else {
// 执行renderComponentRoot,转为vnode
const subTree = (instance.subTree = renderComponentRoot(instance))
// 执行patch
patch(
null,
subTree,
container,
anchor,
instance,
parentSuspense,
isSVG
)
initialVNode.el = subTree.el
}
// mounted hook
if (m) {
queuePostRenderEffect(m, parentSuspense)
}
// onVnodeMounted
if (
!isAsyncWrapperVNode &&
(vnodeHook = props && props.onVnodeMounted)
) {
const scopedInitialVNode = initialVNode
queuePostRenderEffect(
() => invokeVNodeHook(vnodeHook!, parent, scopedInitialVNode),
parentSuspense
)
}
// isMounted置为true,之后进来就走unpate分支了
instance.isMounted = true
// #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
// Disallow component effect recursion during pre-lifecycle hooks.
toggleRecurse(instance, false)
if (next) {
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)
}
toggleRecurse(instance, true)
// render
// 执行renderComponentRoot,转化为vnode
const nextTree = renderComponentRoot(instance)
const prevTree = instance.subTree
instance.subTree = nextTree
// patch更新
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
)
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) {
queuePostRenderEffect(u, parentSuspense)
}
// onVnodeUpdated
if ((vnodeHook = next.props && next.props.onVnodeUpdated)) {
queuePostRenderEffect(
() => invokeVNodeHook(vnodeHook!, parent, next!, vnode),
parentSuspense
)
}
}
}
componentUpdateFn方法做了什么?
- 判断isMounted变量
- 如果isMounted为false,代表没有挂载,执行renderComponentRoot方法,转化为vnode赋值给subtree,然后执行patch(null, subtree)方法进行挂载
- 如果isMounted为true,代表已经挂载,执行renderComponentRoot方法,转化为vnode赋值给subtree,然后patch(oldsubtree, subtree)方法进行更新
因此这个更新函数就是将组件转为vnode,并执行patch方法执行相应的挂载更新操作
注意: 这里调用patch,是递归调用哦
因为我们是通过patch方法进入到这里的,这里又再次调用了patch,因此这是明显的递归调用
那么这次调用patch,是不是就又要根据类型判断执行相对应的一系列操作,之后又会走到这里执行patch方法?
这是不是形成了一个圈,在组件树经过patch执行process操作之后,来到这里继续递归patch子节点,直到整个节点树被递归完毕?
好!先在这里留下猜测:递归的patch
更新机制
创建更新机制 路径: core\packages\runtime-core\src\renderer.ts
const effect = (instance.effect = new ReactiveEffect(
componentUpdateFn,
() => queueJob(instance.update),
instance.scope // track it in component's effect scope
))
这一步做了什么?
这个涉及到响应式和渲染任务队列,因此我只在这先说作用,后续会细说(组件更新流程)
创建effect,当组件更新时(触发依赖更新),会去调用我们之前创建的更新函数componentUpdateFn
componentUpdateFn更新函数内部会去执行renderComponentRoot方法,然后执行patch方法递归更新
首次更新视图
update方法 路径: core\packages\runtime-core\src\renderer.ts
const update = (instance.update = effect.run.bind(effect) as SchedulerJob)
update.id = instance.uid
// allowRecurse
// #1801, #2043 component render effects should allow recursive updates
toggleRecurse(instance, true)
// 首次更新视图
update()
update方法做了什么?
这里的update方法其实是effect.run方法,这里也是涉及到响应式的effect类的run方法,所以我先说功能
其实这个run方法就是调用我们的更新函数,所以这里的update的执行也就是执行componentUpdateFn
componentUpdateFn更新函数内部会去执行renderComponentRoot方法,然后执行patch方法递归,因为初始化组件,n1都为null,所以会执行递归挂载操作
在update执行完之后,视图渲染完毕,用户就能看到了
组件初始化完成
到此,组件初次挂载也就是初始化完成
总结
这一章做了什么?
- createComponentInstance方法创建组件实例instance
- setupComponent方法初始化组件实例
- setupRendererEffect方法创建组件更新函数,创建更新机制,首次视图更新
这一章的todo如图