createApp源码

源码位置:https://github.com/vuejs/vue-next/blob/master/packages/runtime-dom/src/index.ts

都是源码,比较干。。。

v 3.0.4

代码实现:

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
/**
* createApp 函数
*/
export const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args)

// 开发环境 校验 组件的 name 是不是内置的标签同名
if (__DEV__) {
injectNativeTagCheck(app)
}

const { mount } = app
/**
* 重写了 mount 函数
* @param containerOrSelector
*/
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
// container 是真实的 DOM 元素
const container = normalizeContainer(containerOrSelector)
if (!container) return
const component = app._component // 组件的options
// 默认 组件的 template 是 挂载元素的内容
if (!isFunction(component) && !component.render && !component.template) {
component.template = container.innerHTML
}
// 清空 容器中的内容
container.innerHTML = ''
const proxy = mount(container)
if (container instanceof Element) {
// 删除元素上的 v-cloak 指令
container.removeAttribute('v-cloak')
container.setAttribute('data-v-app', '')
}
return proxy
}

return app
}) as CreateAppFunction<Element>

调用 ensureRenderer().createApp(...args) 方法,获取到了 app 的实例;然后重写了 appmount 方法,在新的 mount 方法中,先对 container 做了一次处理(如果传的是css选择器,通过 document.querySelector 方法获取到的DOM元素 ),让 container 是一个真实的 DOM元素

在组件不是一个函数,并且没有设置 render 函数和 tempalte 参数的时候,默认 container 中的 innerHTML 就是组件的 template

调用 app 中返回的 mount 方法,完成 DOM 的挂载

ensureRenderer

1
2
3
4
5
6
/**
* 惰性 创建 renderer
*/
function ensureRenderer() {
return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))
}

惰性创建 renderer 对象,这个对象的创建和运行的平台有关系,在 WEB 平台时,参数 rendererOptions 这个是 DOM 操作的 API

createRenderer

1
2
3
4
5
6
export function createRenderer<
HostNode = RendererNode,
HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
return baseCreateRenderer<HostNode, HostElement>(options)
}

这个方法直接返回了 baseCreateRenderer 方法,baseCreateRenderer 方法有几个重载的方法。

baseCreateRenderer

源码位置: https://github.com/vuejs/vue-next/blob/master/packages/runtime-core/src/renderer.ts

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
function baseCreateRenderer(
options: RendererOptions,
createHydrationFns?: typeof createHydrationFunctions
): any {
// WEB 平台获取到的是操作 DOM 的方法
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 patch = () => {...}
const processText = () => {...}
const processCommentNode = () => {...}
const mountStaticNode = () => {...}
const patchStaticNode = () => {...}
const moveStaticNode = () => {...}
const removeStaticNode = () => {...}
const processElement = () => {...}
const mountElement = () => {...}
const setScopeId = () => {...}
const mountChildren = () => {...}
const patchElement = () => {...}
const patchBlockChildren = () => {...}
const patchProps = () => {...}
const processFragment = () => {...}
const processComponent = () => {...}
const mountComponent = () => {...}
const updateComponent = () => {...}
const setupRenderEffect = () => {...}
const updateComponentPreRender = () => {...}
const patchChildren = () => {...}
const patchUnkeyedChildren = () => {...}
const patchKeyedChildren = () => {...}
const move = () => {...}
const unmount = () => {...}
const remove = () => {...}
const removeFragment = () => {...}
const unmountComponent = () => {...}
const unmountChildren = () => {...}
const getNextHostNode = () => {...}
const render = () => {...}
return {
render,
hydrate,
// createApp 入口
createApp: createAppAPI(render, hydrate)
}
}

createApp 中调用了 ensureRenderer().createApp(...args) 方法获取 app 的实例,就是 baseCreateRenderer 返回的对象中的 createApp 函数,通过 createAppAPI 函数生成的一个函数

createAppAPI

源码位置:https://github.com/vuejs/vue-next/blob/master/packages/runtime-core/src/apiCreateApp.ts

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
/**
* 返回 app 实例
* @param render
* @param hydrate
*/
export function createAppAPI<HostElement>(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
/**
* 接收两个参数
* rootComponent 根组件
* rootProps 传递给根组件的 props
*/
return function createApp(rootComponent, rootProps = null) {
const context = createAppContext() // 返回一个对象
// 安装的插件
const installedPlugins = new Set()
// 是否挂载
let isMounted = false

const app = (context.app = {
_uid: uid++, // 唯一id
_component: rootComponent as ConcreteComponent,
_props: rootProps,
_container: null,
_context: context,

version, // vue 版本

get config() { // config 是一个只读对象,设置 config 在开发环境会报警告
return context.config
},

use() {...},
mixin() {...},
component() {...},
directive() {...},
mount() {...},
unmount() {...},
provide() {...}
})

return app
}
}

runtime-domcreateApp 中,重写了 mount 方法,在其中调用了 app.mount 方法。

mount

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 组件挂载
* @param rootContainer
* @param isHydrate
*/
mount(rootContainer: HostElement, isHydrate?: boolean): any {
if (!isMounted) {
// 创建 vnode 节点
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
// 节点的 vnode 挂载 context
vnode.appContext = context
// 忽略其他平台的执行,和开发环境的警告
// ...
// 执行 render 函数
render(vnode, rootContainer)
isMounted = true
app._container = rootContainer
// 返回 ???
return vnode.component!.proxy
}
}

mount 方法中,执行了 createVNode 方法创建了一个 组件的 vnode,然后执行了 render 方法。

createVNode

创建 vnode 节点

1
2
3
export const createVNode = (__DEV__
? createVNodeWithArgsTransform
: _createVNode) as typeof _createVNode

不考虑开发环境的情况,直接看 _createVNode 方法

_createVNode

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
/**
* 创建 vnode 的方法
* @param type
* @param props
* @param children
* @param patchFlag
* @param dynamicProps
* @param isBlockNode
*/
function _createVNode(
type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
props: (Data & VNodeProps) | null = null,
children: unknown = null,
patchFlag: number = 0,
dynamicProps: string[] | null = null,
isBlockNode = false
): VNode {
if (isVNode(type)) {
// 如果 type 已经是一个 vnode,返回 clone 的 vnode
const cloned = cloneVNode(type, props, true /* mergeRef: true */)
if (children) {
normalizeChildren(cloned, children)
}
return cloned
}
// 处理 class 组件,vue3 中已经取消了 class 组件
// ...
// class & style normalization.
// 处理 props class 转成 字符串, style 转成 对象

// encode the vnode type information into a bitmap
const shapeFlag = isString(type)
? ShapeFlags.ELEMENT
: __FEATURE_SUSPENSE__ && isSuspense(type)
? ShapeFlags.SUSPENSE
: isTeleport(type)
? ShapeFlags.TELEPORT
: isObject(type)
? ShapeFlags.STATEFUL_COMPONENT
: isFunction(type)
? ShapeFlags.FUNCTIONAL_COMPONENT
: 0
// 新的 vnode 节点
const vnode: VNode = {
__v_isVNode: true,
[ReactiveFlags.SKIP]: true,
type,
props,
key: props && normalizeKey(props),
ref: props && normalizeRef(props),
scopeId: currentScopeId,
children: null,
component: null,
suspense: null,
ssContent: null,
ssFallback: null,
dirs: null,
transition: null,
el: null,
anchor: null,
target: null,
targetAnchor: null,
staticCount: 0,
shapeFlag,
patchFlag,
dynamicProps,
dynamicChildren: null,
appContext: null
}

// validate key 校验 key 不是 NaN
// ...
// 处理子节点
normalizeChildren(vnode, children)
if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
const { content, fallback } = normalizeSuspenseChildren(vnode)
vnode.ssContent = content
vnode.ssFallback = fallback
}

if (
shouldTrack > 0
!isBlockNode &&
currentBlock &&
(patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
patchFlag !== PatchFlags.HYDRATE_EVENTS
) {
currentBlock.push(vnode)
}
return vnode
}

这个方法返回了一个新的 vnode,即使传入的参数已经是一个 vnode 节点,也会 clone 一份新的 vnode 节点,并返回。

然后看在 mount 中调用的另一个方法 render

render

mount中调用的这个 render 方法是在调用 createAppAPI 方法 的时候,传入的参数。也就是 baseCreateRenderer 方法中定义的 render 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const render: RootRenderFunction = (vnode, container) => {
// vnode === null 卸载组件
if (vnode == null) {
if (container._vnode) {
// 卸载组件需要执行
unmount(container._vnode, null, null, true)
}
} else {
// patch 更新和或者创建组件
patch(container._vnode || null, vnode, container)
}
flushPostFlushCbs()
// 保存组件的 vnode
container._vnode = vnode
}

render 方法中通过 patch 方法,将 vnode 转化成真实的 DOM,并挂载在页面上。

patch

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
const patch: PatchFn = (
n1,
n2,
container,
anchor = null,
parentComponent = null,
parentSuspense = null,
isSVG = false,
optimized = false
) => {
// 新旧节点类型不同的时候,直接删除旧的节点
if (n1 && !isSameVNodeType(n1, n2)) {
anchor = getNextHostNode(n1)
unmount(n1, parentComponent, parentSuspense, true)
n1 = null
}
const { type, ref, shapeFlag } = n2
switch (type) {
case Text:
// ...
break
case Comment:
// ...
break
case Static:
// ...
break
case Fragment:
// ...
break
default:
// 处理其他类型的节点
// ...
if (shapeFlag & ShapeFlags.COMPONENT) {
processComponent( // 处理component
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
}
// 处理 绑定的 ref
if (ref != null && parentComponent) {
setRef(ref, n1 && n1.ref, parentSuspense, n2)
}
}

patch 过程中,会根据 vnodetype 不同,调用不同的处理节点的方法,这里主要看处理 component 的方法 processComponent,因为这里会执行 setup 和收集依赖。

processComponent

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
const processComponent = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
optimized: boolean
) => {
if (n1 == null) {
// ...
// 挂载 component
mountComponent(
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else {
// 更新组件
updateComponent(n1, n2, optimized)
}
}

可以看出创建组件使用了 mountComponent 方法,更新组件使用了 updateComponent 方法,先看 mountComponent,再看 updateComponent

mountComponent

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
const mountComponent: MountComponentFn = (
initialVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
// createComponentInstance 会对 instance 做处理, ctx 的不一致,就是在这个方法处理的
const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
// ...
// 这里调用 setup 方法,setup 返回的值,会保存在 instance.setupState 中
setupComponent(instance)
// ...
if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
// 处理 setup 是 promise 的情况,在 promise 的状态 resolve 之后,才执行 setupRenderEffect 函数
parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect)
// ...
return
}

setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
)
}

这个方法里通过 createComponentInstance 生成了 instance

这个 instance 是通过 getCurrentInstance 获取到的 instanceinstancectx 属性,在 dev 环境和 prod 是两个东西,生产环境中不要使用

函数最后执行了 setupRenderEffect 方法,在这个方法的执行过程中,会收集 vnode 中使用到的依赖

createComponentInstance
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export function createComponentInstance(
vnode: VNode,
parent: ComponentInternalInstance | null,
suspense: SuspenseBoundary | null
) {
// ...
const instance: ComponentInternalInstance = {
// ...
}
// 开发环境对 ctx 做的特殊处理
// 项目开发中不能使用这个 ctx,生产环境不支持
if (__DEV__) {
instance.ctx = createRenderContext(instance)
} else {
instance.ctx = { _: instance }
}
instance.root = parent ? parent.root : instance
instance.emit = emit.bind(null, instance)

return instance
}

setupRenderEffect

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
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
) => {
instance.update = effect(function componentEffect() {
// 新建组件
if (!instance.isMounted) {
let vnodeHook: VNodeHook | null | undefined
const { el, props } = initialVNode
// 对子节点处理 这里会执行 组件的 render 函数
// render 函数对 ref / reactive 的值的获取,都会把当前函数作为依赖变更需要触发的函数收集
const subTree = (instance.subTree = renderComponentRoot(instance))
patch( // 这个patch执行会完成DOM的挂载
null,
subTree,
container,
anchor,
instance,
parentSuspense,
isSVG
)
initialVNode.el = subTree.el
instance.isMounted = true
initialVNode = container = anchor = null as any
} else {
// 更新组件
let { next, bu, u, parent, vnode } = instance
let originNext = next
let vnodeHook: VNodeHook | null | undefined
const nextTree = renderComponentRoot(instance)
const prevTree = instance.subTree
instance.subTree = 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
)
next.el = nextTree.el
}
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
}

setupRenderEffect 函数执行会调用 effect 函数,只有在 effect 中执行的函数,才可以做依赖收集,通过 renderComponentRoot 方法创建组件的子节点,这个方法执行了组件的 render 方法,render方法中对 reactive 类型的值的获取和 ref / computed 类型的 .value 的获取,都会把这个 effect 函数作为变更的依赖做收集。

在执行 effect 的时候,传递了第二个参数 prodEffectOptions,这个参数中,有一个 scheduler 方法,这个是依赖更新之后会调用的调度器,这个调度器决定什么时候执行 DOM 更新,而不是每次依赖变化都对 DOM 做修改。

在这个方法也会执行 beforeMount 的 hooks 函数,之后执行的 renderComponentRoot 结束之后,再次执行一个 patch 方法,这个方法中,完成了组件创建到 DOM 的动作。并且对组件模板中绑定的 ref 做处理。通过 setRef 方法。

在之后执行了 mounted 的 hooks 函数。

组件的渲染就结束了!!!