上一篇说到 mountComponent的主要核心就是 createComponentInstance,setupComponent,setupRenderEffect 这三个函数的调用,这篇就分别探讨一下这三个函数的使用create
目录
1.createComponentInstance
createComponetInstance主要就是创建组件实例instance
//调用createComponentInstance
const instance: ComponentInternalInstance =
compatMountInstance ||
(initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
//解析
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 resolved 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,
attrsProxy: null,
slotsProxy: 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
}
// 处理 ctx
if (__DEV__) { //开发模式
instance.ctx = createDevRenderContext(instance)
} else { //ctx._ 存放组件实例
instance.ctx = { _: instance }
}
// 赋值 emit 和 root
// 如果实例有parent root为parent的root,没有root就为该实例
instance.root = parent ? parent.root : instance
// 这里使用 bind 把 instance的进行绑定(方便用户获取instance实例)
// 后面用户使用的时候只需要给 event 和参数即可
instance.emit = emit.bind(null, instance)
// apply custom element special handling
if (vnode.ce) {
vnode.ce(instance)
}
return instance
}
2.setupComponent
基于之前创建好的instance进行setupComponent处理,
主要就是初始化props,slots,然后执行setupStatefulComponent函数将返回的值赋值给setupResult(处理render,setup,template的关键)
//基于instance进行处理
setupComponent(instance)
//setupComponent
export function setupComponent(
instance: ComponentInternalInstance,
isSSR = false
) {
isInSSRComponentSetup = isSSR
const { props, children } = instance.vnode
const isStateful = isStatefulComponent(instance)
// 处理props
initProps(instance, props, isStateful, isSSR)
// 处理 slots
initSlots(instance, children)
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)
: undefined
isInSSRComponentSetup = false
return setupResult
}
2.1 setupStatefulComponent
setupStatefulComponent主要的核心就是将instance.proxy做了一个proxy代理,其实proxy 对象其实是代理了 instance.ctx 对象(通过new Proxy(instance.ctx, PublicInstanceProxyHandlers))
然后调用检查组件是否有setup函数,并且调用传入props,context两个参数,将结果存到setupResult(主要目的--Vue支持setup中返回h函数进行模板渲染),如果setup有值最后会执行 handleSetupResult,如果没有值就会执行 finishComponentSetup
//setupStatefulComponent
function setupStatefulComponent(
instance: ComponentInternalInstance,
isSSR: boolean
) {
const Component = instance.type as ComponentOptions
if (__DEV__) {
if (Component.name) {
validateComponentName(Component.name, instance.appContext.config)
}
if (Component.components) {
const names = Object.keys(Component.components)
for (let i = 0; i < names.length; i++) {
validateComponentName(names[i], instance.appContext.config)
}
}
if (Component.directives) {
const names = Object.keys(Component.directives)
for (let i = 0; i < names.length; i++) {
validateDirectiveName(names[i])
}
}
if (Component.compilerOptions && isRuntimeOnly()) {
warn(
`"compilerOptions" is only supported when using a build of Vue that ` +
`includes the runtime compiler. Since you are using a runtime-only ` +
`build, the options should be passed via your build tool config instead.`
)
}
}
// 0.创建一个资源的缓存池
// 0. create render proxy property access cache
instance.accessCache = Object.create(null)
// proxy 对象其实是代理了 instance.ctx 对象
// 我们在使用的时候需要使用 instance.proxy 对象
// 1. create public instance / render proxy
// also mark it raw so it's never observed
instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))
if (__DEV__) {
exposePropsOnRenderContext(instance)
}
// 调用setup函数
// 2. call setup()
const { setup } = Component
if (setup) {
//初始化setup中的context
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null)
//设置当前调用setup实例
setCurrentInstance(instance)
pauseTracking()
// 调用setup函数 并传入props,context两个参数,并将结果存到setupResult
const setupResult = callWithErrorHandling(
setup,
instance,
ErrorCodes.SETUP_FUNCTION,
// 如果是dev开发环境,将props转为已读
[__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
)
resetTracking()
unsetCurrentInstance()
// 3. 处理 setupResult
if (isPromise(setupResult)) {
setupResult.then(unsetCurrentInstance, unsetCurrentInstance)
if (isSSR) {
// return the promise so server-renderer can wait on it
return setupResult
.then((resolvedResult: unknown) => {
handleSetupResult(instance, resolvedResult, isSSR)
})
.catch(e => {
handleError(e, instance, ErrorCodes.SETUP_FUNCTION)
})
} else if (__FEATURE_SUSPENSE__) {
// async setup returned Promise.
// bail here and wait for re-entry.
instance.asyncDep = setupResult
if (__DEV__ && !instance.suspense) {
const name = Component.name ?? 'Anonymous'
warn(
`Component <${name}>: setup function returned a promise, but no ` +
`<Suspense> boundary was found in the parent component tree. ` +
`A component with async setup() must be nested in a <Suspense> ` +
`in order to be rendered.`
)
}
} else if (__DEV__) {
warn(
`setup() returned a Promise, but the version of Vue you are using ` +
`does not support it yet.`
)
}
} else {
handleSetupResult(instance, setupResult, isSSR)
}
} else {
finishComponentSetup(instance, isSSR)
}
}
2.2 handleSetupResult
handleSetupResult其实就是根据setupResult(setup 返回值)的值进行不同的处理,如果setup返回的是函数,那么就会将setupResult赋值给instance.render (就是 vue提到 setup可以和渲染函数一起使用),如果只是返回对象的话,就使用 proxyRefs 做一层 proxy代理---可以方便用户直接获取ref值 (shallowUnwrapHandlers的内部getter和setter做了相关处理,getter做了unRef处理,这里暂时不讲解),但是最终handleSetupResult还是会执行 finishComponentSetup函数
// 如果 objectWithRefs已经是reactive,返回本身
// 如果不是,给他一个proxy处理(做一个响应式处理)
export function proxyRefs<T extends object>(
objectWithRefs: T
): ShallowUnwrapRef<T> {
return isReactive(objectWithRefs)
? objectWithRefs
: new Proxy(objectWithRefs, shallowUnwrapHandlers)
}
export function handleSetupResult(
instance: ComponentInternalInstance,
setupResult: unknown,
isSSR: boolean
) {
// setup 返回值不一样的话,会有不同的处理
if (isFunction(setupResult)) {
// 如果返回的是 function 的话,那么绑定到 render 上
// 认为是 render 逻辑
// setup(){ return ()=>(h("div")) }
// setup returned an inline render function
if (__SSR__ && (instance.type as ComponentOptions).__ssrInlineRender) {
// 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
}
} else if (isObject(setupResult)) {
if (__DEV__ && isVNode(setupResult)) {
warn(
`setup() should not return VNodes directly - ` +
`return a render function instead.`
)
}
// 如果返回的是 object
// 将结果先存到setupState上
// proxyRefs 的作用就是把 setupResult 对象做一层proxy代理
// 方便用户直接访问 ref 类型的值
// 比如 setupResult 里面有个 count 是个 ref 类型的对象,用户使用的时候就可以直接使用 count 了,而不需要在 count.value
// setup returned bindings.
// assuming a render function compiled from template is present.
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
instance.devtoolsRawSetupState = setupResult
}
instance.setupState = proxyRefs(setupResult)
if (__DEV__) {
exposeSetupStateOnRenderContext(instance)
}
} else if (__DEV__ && setupResult !== undefined) {
warn(
`setup() should return an object. Received: ${setupResult === null ? 'null' : typeof setupResult
}`
)
}
finishComponentSetup(instance, isSSR)
}
2.3 finishComponentSetup
finishComponentSetup 是 setupStatefulComponent函数的最后一步,可以发现我们现在已经对setup的返回值做了不同处理,finishComponentSetup 的函数主要目的就是给组件实例找到一个真正的render
它先判断组件是否存在渲染函数 render (排除了我们手动设置render以及setup的返回),如果不存在则判断是否存在 template 选项,将 template 编译成渲染函数。
总的来说,它会在 render,setup,template进行一个选择,选出一个真正的render函数
export function finishComponentSetup(
instance: ComponentInternalInstance,
isSSR: boolean,
skipOptions?: boolean
) {
// 获取设置组件函数--用户设置的 component options
const Component = instance.type as ComponentOptions
if (__COMPAT__) {
convertLegacyRenderFn(instance)
if (__DEV__ && Component.compatConfig) {
validateCompatConfig(Component.compatConfig)
}
}
// template / render function normalization
// could be already set when returned from setup()
// 如果instance中没有render---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) {
//如果compule有值,组件没有传render函数--进行模板解析
//会将模板解析的值赋值给Component.render
const template =
(__COMPAT__ &&
instance.vnode.props &&
instance.vnode.props['inline-template']) ||
Component.template ||
resolveMergedOptions(instance).template
if (template) {
if (__DEV__) {
startMeasure(instance, `compile`)
}
const { isCustomElement, compilerOptions } = instance.appContext.config
const { delimiters, compilerOptions: componentCompilerOptions } =
Component
const finalCompilerOptions: CompilerOptions = extend(
extend(
{
isCustomElement,
delimiters
},
compilerOptions
),
componentCompilerOptions
)
if (__COMPAT__) {
// pass runtime compat config into the compiler
finalCompilerOptions.compatConfig = Object.create(globalCompatConfig)
if (Component.compatConfig) {
// @ts-expect-error types are not compatible
extend(finalCompilerOptions.compatConfig, Component.compatConfig)
}
}
// 将组件的render设置为模板解析后的render
Component.render = compile(template, finalCompilerOptions)
if (__DEV__) {
endMeasure(instance, `compile`)
}
}
}
//直接将组件的render函数作为instance的render()
instance.render = (Component.render || NOOP) as InternalRenderFunction
// for runtime-compiled render functions using `with` blocks, the render
// proxy used needs a different `has` handler which is more performant and
// also only allows a whitelist of globals to fallthrough.
if (installWithProxy) {
installWithProxy(instance)
}
}
// support for 2.x options
if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {
setCurrentInstance(instance)
pauseTracking()
try {
applyOptions(instance)
} finally {
resetTracking()
unsetCurrentInstance()
}
}
// warn missing template/render
// the runtime compilation of template in SSR is done by server-render
if (__DEV__ && !Component.render && instance.render === NOOP && !isSSR) {
/* istanbul ignore if */
if (!compile && Component.template) {
warn(
`Component provided template option but ` +
`runtime compilation is not supported in this build of Vue.` +
(__ESM_BUNDLER__
? ` Configure your bundler to alias "vue" to "vue/dist/vue.esm-bundler.js".`
: __ESM_BROWSER__
? ` Use "vue.esm-browser.js" instead.`
: __GLOBAL__
? ` Use "vue.global.js" instead.`
: ``) /* should not happen */
)
} else {
warn(`Component is missing template or render function.`)
}
}
}
3.setupRenderEffect
之前说到mountComponent的主要核心就是 createComponentInstance,setupComponent,setupRenderEffect 这三个函数的调用
setupRenderEffect 就是最后一步 ,之前已经知道setupComponent 已经为我们选择了一个正确的render,以及对setup函数返回结果做了proxy代理,setupRenderEffect 主要作用就是依赖收集
核心就是 componentUpdateFn,他会创建一个reativeEffect 去关联componentUpdateFn,每当组件需要更新的时候就会触发componentUpdateFn实现组件的更新
我们需要讨论的就是componentUpdateFn以及reativeEffect 的作用,由于篇幅过长需要另开一篇
//调用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()
}