分析以下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)
}
}
複製代碼