结合Vue3-demo项目分析源码(二)

结合Vue3-demo项目分析源码(一),创建完App组件,开始使用mount方法渲染app

分析

mount()

渲染app

查看源码项目vue-next/packages/runtime-dom/src/index.tscreateApp(),其中app.mount被重写了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
app.mount = (containerOrSelector: Element | string): any => {
//根据传递的选择器获取dom元素
const container = normalizeContainer(containerOrSelector)
if (!container) return
const component = app._component //若不存在,使用app根组件
if (!isFunction(component) && !component.render && !component.template) {
component.template = container.innerHTML
}
// clear content before mounting
container.innerHTML = ''
//调用app对象原始mount方法,传递给当前根节点元素
const proxy = mount(container)
container.removeAttribute('v-cloak')
container.setAttribute('data-v-app', '')
return proxy
}

由此可见,重写的mount()根据传递的选择器找到dom元素,调用原始mount方法,传递给当前根节点元素。

查看文件vue-next/packages/runtime-core/src/apiCreateApp.ts第227行原始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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
mount(rootContainer: HostElement, isHydrate?: 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//节点上下文为根节点上下文

// HMR root reload
if (__DEV__) {
context.reload = () => {
render(cloneVNode(vnode), rootContainer)
}
}
//支持自定义渲染
if (isHydrate && hydrate) {
//自定义渲染
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
//默认渲染
render(vnode, rootContainer)
}
isMounted = true
app._container = rootContainer
// for devtools and telemetry
;(rootContainer as any).__vue_app__ = app

if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
devtoolsInitApp(app, version)
}

return vnode.component!.proxy
} else if (__DEV__) {
//多次调用mount方法报错
warn(
`App has already been mounted.\n` +
`If you want to remount the same app, move your app creation logic ` +
`into a factory function and create fresh app instances for each ` +
`mount - e.g. \`const createMyApp = () => createApp(App)\``
)
}

原始mount方法主要创建一个vnode节点,调用render()方法渲染。

render()

渲染节点

查看文件vue-next/packages/runtime-core/src/renderer.ts第2182行

1
2
3
4
5
6
7
8
9
10
11
12
13
const render: RootRenderFunction = (vnode, container) => {
//若当前节点为null,则卸载节点
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
//使用patch方法对比新老节点
patch(container._vnode || null, vnode, container)
}
flushPostFlushCbs()
container._vnode = vnode
}

patch()

对比新老节点,根据节点类型的不同做不同处理

查看文件vue-next/packages/runtime-core/src/renderer.ts第448行

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
92
93
94
95
96
97
98
99
100
const patch: PatchFn = (
n1,
n2,
container,
anchor = null,
parentComponent = null,
parentSuspense = null,
isSVG = false,
optimized = false
) => {
// patching & not same type, unmount old tree
//对比新老节点,若类型一样,则卸载老节点
if (n1 && !isSameVNodeType(n1, n2)) {
anchor = getNextHostNode(n1)
//卸载老节点
unmount(n1, parentComponent, parentSuspense, true)
n1 = null
}

if (n2.patchFlag === PatchFlags.BAIL) {
optimized = false
n2.dynamicChildren = null
}

const { type, ref, shapeFlag } = n2
//根据当前节点类型不同做不同处理
switch (type) {
case Text: //文本节点TextNode
processText(n1, n2, container, anchor)
break
case Comment: // 文档注释节点
processCommentNode(n1, n2, container, anchor)
break
case Static: //svg等静态节点
if (n1 == null) {
mountStaticNode(n2, container, anchor, isSVG)
} else if (__DEV__) {
patchStaticNode(n1, n2, container, isSVG)
}
break
case Fragment: //片段节点
processFragment(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
break
default:
//普通的dom节点-div、span等
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else if (shapeFlag & ShapeFlags.COMPONENT) {//Vue组件
processComponent(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else if (shapeFlag & ShapeFlags.TELEPORT) {//TELEPORT类型
;(type as typeof TeleportImpl).process(
n1 as TeleportVNode,
n2 as TeleportVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized,
internals
)
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
...
} else if (__DEV__) {
...
}
}

// set ref
if (ref != null && parentComponent) {
setRef(ref, n1 && n1.ref, parentSuspense, n2)
}
}

根据main.js中代码得知,appvue组件节点,故下一步到processComponent()方法

processComponent()

查看文件vue-next/packages/runtime-core/src/renderer.ts第1185行

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 processComponent = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
optimized: boolean
) => {
//老节点是否为null(第一次进来的老节点一定是null)
if (n1 == null) {
// 是否是COMPONENT_KEPT_ALIVE类型的节点
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
;(parentComponent!.ctx as KeepAliveContext).activate(
n2,
container,
anchor,
isSVG,
optimized
)
} else { //普通组件
mountComponent(
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
} else {//若老节点不为空,更新组件
updateComponent(n1, n2, optimized)
}
}

app为首次渲染为null且为普通组件,故调用mountomponent()方法

mountComponent()

查看文件vue-next/packages/runtime-core/src/renderer.ts第1220行

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
const mountComponent: MountComponentFn = (
initialVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
//创建组件实例
const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
...
//设置组件实例
setupComponent(instance)
...
//处理setup方法
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
)
...
}

createComponentInstance()

创建组件实例

查看文件vue-next/packages/runtime-core/src/component.ts第405行

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
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, //当前组件的vnode节点
type, //组件类型
parent, //父组件
appContext, //app上下文
root: null!, // to be immediately set
next: null,
subTree: null!, // will be set synchronously right after creation
update: null!, // will be set synchronously right after creation
render: null,
proxy: null, //组件实例代理对象(组件里用this访问的即为此对象
exposed: null,
withProxy: null,
effects: null,
provides: parent ? parent.provides : Object.create(appContext.provides),
accessCache: null!,
renderCache: [],

// local resovled assets
components: null,
directives: null,

// resolved props and emits options
propsOptions: normalizePropsOptions(type, appContext),
emitsOptions: normalizeEmitsOptions(type, appContext),

// emit
emit: null as any, // to be set immediately
emitted: null,

// state
ctx: EMPTY_OBJ,
data: EMPTY_OBJ,
props: EMPTY_OBJ,
attrs: EMPTY_OBJ,
slots: EMPTY_OBJ,
refs: EMPTY_OBJ,
setupState: EMPTY_OBJ,
setupContext: 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
}
...
instance.root = parent ? parent.root : instance
instance.emit = emit.bind(null, instance)
...
//返回实例
return instance
}

组件实例中proxy即为在组件里用this访问的对象。Vue只暴露组件实例的一部分属性供访问,这些属性只能通过代理对象proxy获取。

App.vue中加入代码

1
2
3
4
5
6
7
8
9
10
<script>
export default {
name: "app",
props: ["msg"],
created(){
console.log(this)
console.log("this对象如上")
}
};
</script>

则在浏览其中可看到:

this对象

附录-Vue3流程图

vue流程图

  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2020-2024 Aweso Lynn
  • PV: UV: