分析以下mount
过程vue
const App = {
template: `<div>{{counter.num}}</div><button @click="add">增长</button>`,
setup() {
const counter = reactive({ num: 0 })
function add() {
counter.num ++;
}
return {
counter,
add
}
}
}
createApp().mount(App, '#app');
复制代码
先上一个流程图,而后对照源码一步一步理解。 node
生成返回一个
App
对象,对象中包括mount、use、mixin、component、directive
等方法。这里主要分析mount
方法react
return createApp(): App {
// 建立components、derectives等上下文
const context = createAppContext()
let isMounted = false
const app: App = {
use(plugin: Plugin) {
// ...
return app
},
// 核心函数mount
mount(
rootComponent: Component,
rootContainer: HostElement,
rootProps?: Data
): any {
if (!isMounted) {
// 生成vnode: rootComponent对象。rootProps:数据
const vnode = createVNode(rootComponent, 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
return vnode.component!.renderProxy
}
}
}
return app
}
复制代码
生成Vnode对象,将class和style标准化,bash
export function createVNode(
type: VNodeTypes,
props: { [key: string]: any } | null = null,
children: unknown = null,
patchFlag: number = 0,
dynamicProps: string[] | null = null
): VNode {
// class和style标准化
// class & style normalization.
if (props !== null) {
// for reactive or proxy objects, we need to clone it to enable mutation.
if (isReactive(props) || SetupProxySymbol in props) {
// proxy对象还原成源对象,extend相似于Object.assign
props = extend({}, props)
}
let { class: klass, style } = props
if (klass != null && !isString(klass)) {
props.class = normalizeClass(klass)
}
if (style != null) {
// reactive state objects need to be cloned since they are likely to be
// mutated
if (isReactive(style) && !isArray(style)) {
style = extend({}, style)
}
props.style = normalizeStyle(style)
}
}
// 判断vnode类型
// encode the vnode type information into a bitmap
const shapeFlag = isString(type)
? ShapeFlags.ELEMENT
: __FEATURE_SUSPENSE__ && isSuspenseType(type)
? ShapeFlags.SUSPENSE
: isObject(type)
? ShapeFlags.STATEFUL_COMPONENT
: isFunction(type)
? ShapeFlags.FUNCTIONAL_COMPONENT
: 0
const vnode: VNode = {
_isVNode: true, // 用来判断是不是vnode
type,
props,
key: (props !== null && props.key) || null,
ref: (props !== null && props.ref) || null,
children: null,
component: null,
suspense: null,
dirs: null,
el: null,
anchor: null,
target: null,
shapeFlag,
patchFlag,
dynamicProps,
dynamicChildren: null,
appContext: null
}
// 将children挂到vnode.children上
normalizeChildren(vnode, children)
// ...
return vnode
}
复制代码
核心函数patchapp
const render: RootRenderFunction<
HostNode,
HostElement & {
_vnode: HostVNode | null
} > = (vnode, container) => {
if (vnode == null) {
if (container._vnode) {
// !vnode && container上已挂载vnode,卸载vnode
unmount(container._vnode, null, null, true)
}
} else {
// 核心函数patch,将vnode挂载到container上
patch(container._vnode || null, vnode, container)
}
flushPostFlushCbs()
container._vnode = vnode
}
复制代码
patch
用于比较两个vnode
的不一样,将差别部分已打补丁的形式更新到页面上。mount
过程时n1为null
。本例直接挂载的类型是COMPONENT
,核心函数为processComponent
async
function patch(
n1: HostVNode | null, // null means this is a mount
n2: HostVNode,
container: HostElement,
anchor: HostNode | null = null,
parentComponent: ComponentInternalInstance | null = null,
parentSuspense: HostSuspenseBoundary | null = null,
isSVG: boolean = false,
optimized: boolean = false
) {
// patching & not same type, unmount old tree
// 若是tag类型不一样,卸载老的tree
if (n1 != null && !isSameType(n1, n2)) {
anchor = getNextHostNode(n1)
unmount(n1, parentComponent, parentSuspense, true)
n1 = null
}
const { type, shapeFlag } = n2
switch (type) {
case Text:
processText(n1, n2, container, anchor)
break
case Comment:
processCommentNode(n1, n2, container, anchor)
break
case Fragment:
processFragment(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
break
case Portal:
processPortal(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
break
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else if (shapeFlag & ShapeFlags.COMPONENT) {
// 本例挂载的是component
processComponent(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
;(type as typeof SuspenseImpl).process(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized,
internals
)
} else if (__DEV__) {
warn('Invalid HostVNode type:', n2.type, `(${typeof n2.type})`)
}
}
}
复制代码
判断挂载,调用函数
mountComponent
ide
function processComponent(
n1: HostVNode | null,
n2: HostVNode,
container: HostElement,
anchor: HostNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: HostSuspenseBoundary | null,
isSVG: boolean,
optimized: boolean
) {
if (n1 == null) {
if (n2.shapeFlag & ShapeFlags.STATEFUL_COMPONENT_KEPT_ALIVE) {
;(parentComponent!.sink as KeepAliveSink).activate(
n2,
container,
anchor
)
} else {
// 核心函数mountComponent
mountComponent(
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG
)
}
} else {
// ...
}
复制代码
两个核心函数,
setupStatefulComponent
和setupRenderEffect
。setupStatefulComponent
: 执行setup
,将返回值setupResult
转换为reactive
数据。将template
转化成render
函数。setupRenderEffect
: 调用effect
,render
渲染页面,将依赖存入targetMap
svg
function mountComponent(
initialVNode: HostVNode,
container: HostElement,
anchor: HostNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: HostSuspenseBoundary | null,
isSVG: boolean
) {
const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
initialVNode,
parentComponent
))
if (__DEV__) {
pushWarningContext(initialVNode)
}
const Comp = initialVNode.type as Component
// inject renderer internals for keepAlive
if ((Comp as any).__isKeepAlive) {
const sink = instance.sink as KeepAliveSink
sink.renderer = internals
sink.parentSuspense = parentSuspense
}
// resolve props and slots for setup context
const propsOptions = Comp.props
resolveProps(instance, initialVNode.props, propsOptions)
resolveSlots(instance, initialVNode.children)
// setup stateful logic
if (initialVNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
<!--执行setup-->
setupStatefulComponent(instance, parentSuspense)
}
// setup() is async. This component relies on async logic to be resolved
// before proceeding
if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
if (!parentSuspense) {
if (__DEV__) warn('async setup() is used without a suspense boundary!')
return
}
parentSuspense.registerDep(instance, setupRenderEffect)
// give it a placeholder
const placeholder = (instance.subTree = createVNode(Comment))
processCommentNode(null, placeholder, container, anchor)
initialVNode.el = placeholder.el
return
}
// 默认调用一次effect渲染页面,并将依赖存入targetMap中
setupRenderEffect(
instance,
parentSuspense,
initialVNode,
container,
anchor,
isSVG
)
if (__DEV__) {
popWarningContext()
}
}
复制代码
执行
setup()
。handleSetupResult
将setup的返回值包装成响应式对象。finishComponentSetup
将template
编译成render函数函数
export function setupStatefulComponent(
instance: ComponentInternalInstance,
parentSuspense: SuspenseBoundary | null
) {
const Component = instance.type as ComponentOptions
// ...
const { setup } = Component
if (setup) {
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null)
currentInstance = instance
currentSuspense = parentSuspense
<!-- 执行setup(),能够看出给setup传了两个参数,propsProxy和setupContext-->
const setupResult = callWithErrorHandling(
setup,
instance,
ErrorCodes.SETUP_FUNCTION,
[propsProxy, setupContext]
)
currentInstance = null
currentSuspense = null
if (isPromise(setupResult)) {
if (__FEATURE_SUSPENSE__) {
// async类型的setup处理方式
// async setup returned Promise.
// bail here and wait for re-entry.
instance.asyncDep = setupResult
} else if (__DEV__) {
warn(
`setup() returned a Promise, but the version of Vue you are using ` +
`does not support it yet.`
)
}
return
} else {
<!-- 将setup的返回值包装成响应式对象 -->
handleSetupResult(instance, setupResult, parentSuspense)
}
} else {
finishComponentSetup(instance, parentSuspense)
}
}
复制代码
export function handleSetupResult(
instance: ComponentInternalInstance,
setupResult: unknown,
parentSuspense: SuspenseBoundary | null
) {
if (isFunction(setupResult)) {
// setup returned an inline render function
instance.render = setupResult as RenderFunction
} else if (isObject(setupResult)) {
if (__DEV__ && isVNode(setupResult)) {
warn(
`setup() should not return VNodes directly - ` +
`return a render function instead.`
)
}
// setup returned bindings.
// assuming a render function compiled from template is present.
// setupResult就是setup函数中return的值
// 将setupResult包装成proxy对象
instance.renderContext = reactive(setupResult)
} else if (__DEV__ && setupResult !== undefined) {
warn(
`setup() should return an object. Received: ${
setupResult === null ? 'null' : typeof setupResult
}`
)
}
<!--将template编译为render函数-->
finishComponentSetup(instance, parentSuspense)
}
复制代码
function finishComponentSetup(
instance: ComponentInternalInstance,
parentSuspense: SuspenseBoundary | null
) {
const Component = instance.type as ComponentOptions
if (!instance.render) {
if (__RUNTIME_COMPILE__ && Component.template && !Component.render) {
// __RUNTIME_COMPILE__ ensures `compile` is provided
// 将template转化为render函数
// 在vue.ts中注册了compile函数
Component.render = compile!(Component.template, {
isCustomElement: instance.appContext.config.isCustomElement || NO,
onError(err: CompilerError) {
if (__DEV__) {
const message = `Template compilation error: ${err.message}`
const codeFrame =
err.loc &&
generateCodeFrame(
Component.template!,
err.loc.start.offset,
err.loc.end.offset
)
warn(codeFrame ? `${message}\n${codeFrame}` : message)
}
}
})
}
if (__DEV__ && !Component.render) {
/* istanbul ignore if */
if (!__RUNTIME_COMPILE__ && Component.template) {
warn(
`Component provides template but the build of Vue you are running ` +
`does not support on-the-fly template compilation. Either use the ` +
`full build or pre-compile the template using Vue CLI.`
)
} else {
warn(
`Component is missing${
__RUNTIME_COMPILE__ ? ` template or` : ``
} render function.`
)
}
}
// 将component的render赋值给外层render
// const App = {render: h('div', 'hello world')} =>
// instance.render = h('div', 'hello world')
instance.render = (Component.render || NOOP) as RenderFunction
}
// support for 2.x options
if (__FEATURE_OPTIONS__) {
currentInstance = instance
currentSuspense = parentSuspense
applyOptions(instance, Component)
currentInstance = null
currentSuspense = null
}
if (instance.renderContext === EMPTY_OBJ) {
instance.renderContext = reactive({})
}
}
复制代码
使用effect包裹渲染过程:渲染
instance
到页面,并将全部依赖存入targetMap。从而实现页面响应式。响应式原理可参考这篇文章:juejin.im/post/5da3e3…post
function setupRenderEffect(
instance: ComponentInternalInstance,
parentSuspense: HostSuspenseBoundary | null,
initialVNode: HostVNode,
container: HostElement,
anchor: HostNode | null,
isSVG: boolean
) {
// create reactive effect for rendering
let mounted = false
instance.update = effect(function componentEffect() {
if (!mounted) {
<!--调用render函数(template编译生成),将template转化为subTree-->
const subTree = (instance.subTree = renderComponentRoot(instance))
// beforeMount hook
if (instance.bm !== null) {
invokeHooks(instance.bm)
}
<!--核心函数patch,将subTree渲染到页面上-->
patch(null, subTree, container, anchor, instance, parentSuspense, isSVG)
initialVNode.el = subTree.el
// mounted hook
if (instance.m !== null) {
queuePostRenderEffect(instance.m, parentSuspense)
}
mounted = true
} else {
// ...
}
}
复制代码
将
subTree
渲染到页面上。核心函数processElement
。processElement
中调用mountElement
function mountElement(
vnode: HostVNode,
container: HostElement,
anchor: HostNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: HostSuspenseBoundary | null,
isSVG: boolean,
optimized: boolean
) {
const tag = vnode.type as string
isSVG = isSVG || tag === 'svg'
const el = (vnode.el = hostCreateElement(tag, isSVG))
const { props, shapeFlag } = vnode
if (props != null) {
for (const key in props) {
if (isReservedProp(key)) continue
hostPatchProp(el, key, props[key], null, isSVG)
}
if (props.onVnodeBeforeMount != null) {
invokeDirectiveHook(props.onVnodeBeforeMount, parentComponent, vnode)
}
}
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
hostSetElementText(el, vnode.children as string)
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(
vnode.children as HostVNodeChildren,
el,
null,
parentComponent,
parentSuspense,
isSVG,
optimized || vnode.dynamicChildren !== null
)
}
hostInsert(el, container, anchor)
if (props != null && props.onVnodeMounted != null) {
queuePostRenderEffect(() => {
invokeDirectiveHook(props.onVnodeMounted, parentComponent, vnode)
}, parentSuspense)
}
}
复制代码