# vue3-render简析
基于createApp了解vue3的DOM渲染主流程:
# 执行流程简要说明
createApp({}).mount()
1
- 执行
createApp
,创建实例 - 执行
mount
方法- 执行
createVNode
,创建虚拟DOM - 执行
render
渲染页面patch
更新内容,执行过程中会创建effect
,执行渲染时会进行依赖收集flushPostFlushCbs
执行任务
- 执行
# 1. createRenderer
创建renderer
renderer = createRenderer(rendererOptions)
const renderer = {
render, // 渲染时使用
hydrate,
createApp: createAppAPI(render, hydrate)
}
function createAppAPI( render: RootRenderFunction, hydrate?: RootHydrateFunction) {
return function createApp(rootComponent, rootProps = null) {}
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 2. Vue.createApp
创建实例
renderer.createApp
创建实例app,重写app.mount
方法
export const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args)
const { mount } = app
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {...}
return app
})
1
2
3
4
5
6
2
3
4
5
6
# 3. 执行mount
,获取模板,挂载实例
app.mount = (containerOrSelector) => {
// ... 添加template模板
component.template = container.innerHTML
// ... 执行挂载 => 执行createApp内mount
const proxy = mount(container, false, container instanceof SVGElement);
// ...
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# 4. 执行createApp内mount
createVNode
创建vnode- 执行
render
渲染
context.app = {
mount(...): any {
if (!isMounted) {
// ... 创建vnode
const vnode = createVNode(rootComponent as ConcreteComponent,rootProps)
// ... 执行渲染 其中render是调用createAppAPI时,传入的参数
render(vnode, rootContainer, isSVG)
}
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 5. render渲染
- 执行
patch
方法,内部会依次触发setup
,beforeCreate
,created
,beforeMount
这些钩子 - 执行
flushPostFlushCbs
渲染页面
渲染完成后触发 mounted
钩子
const render: RootRenderFunction = (vnode, container, isSVG) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
patch(container._vnode || null, vnode, container, null, null, null, isSVG)
}
flushPostFlushCbs()
container._vnode = vnode
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 6. mountElement
patch
过程中会执行DOM的创建与修改:baseCreateRenderer
的私有方法mountElement
就是用于DOM创建的
以下为部分源码
# createApp
packages/runtime-dom/src/index.ts:
- 调用
ensureRenderer()
的createApp
方法, 创建实例 - 重写
mount
方法,将实例挂载到dom上
export const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args)
// 获取初始的mount方法
const { mount } = app
// 重写ount方法,内部会调用上面的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
}
// clear content before mounting
container.innerHTML = ''
// 1. 创建vnode
// 2. 挂载
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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# ensureRenderer
packages/runtime-dom/src/index.ts: 获取renderer
函数,tree shakeing相关???
// lazy create the renderer - this makes core renderer logic tree-shakable
// in case the user only imports reactivity utilities from Vue.
let renderer: Renderer<Element> | HydrationRenderer
function ensureRenderer() {
return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# createRenderer
packages/runtime-core/src/renderer.ts: createRenderer仅仅是内部调用baseCreateRenderer
export function createRenderer<
HostNode = RendererNode,
HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
return baseCreateRenderer<HostNode, HostElement>(options)
}
1
2
3
4
5
6
2
3
4
5
6
# baseCreateRenderer
packages/runtime-core/src/renderer.ts: 调用createAppAPI
生成 createApp
、hydrate
、render
方法
为方面阅读,已去除
baseCreateRenderer
内部的私有方法
创建时传入的参数options
const rendererOptions = extend({ patchProp, forcePatchProp }, nodeOps)
- patchProp,forcePatchProp: props处理
- node节点处理: packages/runtime-dom/src/nodeOps.ts
function baseCreateRenderer(
options: RendererOptions,
createHydrationFns?: typeof createHydrationFunctions
): any {
const {
insert: hostInsert,
remove: hostRemove,
patchProp: hostPatchProp,
forcePatchProp: hostForcePatchProp,
createElement: hostCreateElement,
createText: hostCreateText,
createComment: hostCreateComment,
setText: hostSetText,
setElementText: hostSetElementText,
parentNode: hostParentNode,
nextSibling: hostNextSibling,
setScopeId: hostSetScopeId = NOOP,
cloneNode: hostCloneNode,
insertStaticContent: hostInsertStaticContent
} = options
...
const setupRenderEffect = (...) => {...} // 创建effct
// 组件挂载,最终会在patch方法中执行
const mountComponent = (...) => {
...
setupRenderEffect()
...
}
...
const render: RootRenderFunction = (vnode, container, isSVG) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
patch(container._vnode || null, vnode, container, null, null, null, isSVG)
}
flushPostFlushCbs()
container._vnode = vnode
}
const internals: RendererInternals = {
p: patch,
um: unmount,
m: move,
r: remove,
mt: mountComponent,
mc: mountChildren,
pc: patchChildren,
pbc: patchBlockChildren,
n: getNextHostNode,
o: options
}
let hydrate: ReturnType<typeof createHydrationFunctions>[0] | undefined
let hydrateNode: ReturnType<typeof createHydrationFunctions>[1] | undefined
if (createHydrationFns) { // create暂未涉及
;[hydrate, hydrateNode] = createHydrationFns(internals as RendererInternals<
Node,
Element
>)
}
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# setupRenderEffect
创建渲染effect,其scheduler是queueJob
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
) => {
// create reactive effect for rendering
instance.update = effect(
function componentEffect() { ... },
__DEV__ ? createDevEffectOptions(instance) : prodEffectOptions
)
}
const prodEffectOptions = {
scheduler: queueJob,
// #1801, #2043 component render effects should allow recursive updates
allowRecurse: true
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# createAppAPI
packages/runtime-core/src/apiCreateApp.ts: createAppAPI是生成***createApp***的工厂函数
createApp
内部会根据参数初始化context
,并对context.app(实例)
进行初始化
export function createAppAPI<HostElement>(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
if (rootProps != null && !isObject(rootProps)) {
rootProps = null
}
const context = createAppContext()
const installedPlugins = new Set()
let isMounted = false
const app: App = (context.app = {
_uid: uid++,
_component: rootComponent as ConcreteComponent,
_props: rootProps,
_container: null,
_context: context,
version,
get config() {return context.config},
set config(v) { },
use(plugin: Plugin, ...options: any[]) { ... },
mixin(mixin: ComponentOptions) { ... },
component(name: string, component?: Component): any { ... },
directive(name: string, directive?: Directive) { ... },
mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {
if (!isMounted) {
// 创建vnode
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
// store app context on the root VNode.
// this will be set on the root instance on initial mount.
vnode.appContext = context
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
// 渲染
render(vnode, rootContainer, isSVG)
}
// 改变挂载状态
isMounted = true
app._container = rootContainer
// for devtools and telemetry
;(rootContainer as any).__vue_app__ = app
return vnode.component!.proxy
}
},
unmount() {
if (isMounted) {
render(null, app._container)
delete app._container.__vue_app__
}
},
provide(key, value) { ... }
})
return app
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# createAppContext
生成初始化的context
export function createAppContext(): AppContext {
return {
app: null as any,
config: {
isNativeTag: NO,
performance: false,
globalProperties: {},
optionMergeStrategies: {},
isCustomElement: NO,
errorHandler: undefined,
warnHandler: undefined
},
mixins: [],
components: {},
directives: {},
provides: Object.create(null)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19