组件实例创建
组件是什么?Vue 如何表示一个组件的内部状态?
组件实例是什么?
当你写这样的组件:
vue
<template>
<div>{{ message }}</div>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('Hello Vue')
</script>1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Vue 在内部会创建一个组件实例对象,它包含了:
| 属性 | 说明 |
|---|---|
vnode | 虚拟 DOM 节点 |
type | 组件类型定义(包含 render 函数等) |
props | 接收的 props |
emits | 声明的 emits |
slots | 插槽内容 |
setupState | setup() 返回的状态 |
data | data() 返回的状态 |
ctx | 组件上下文(attrs、emit、expose 等) |
mounted | 是否已挂载 |
update | 更新函数 |
创建组件实例
javascript
/**
* 组件实例选项
* @typedef {Object} ComponentInstanceOptions
*/
export function createComponentInstance(vnode) {
// vnode 是虚拟节点,type 是组件配置
const { type: componentOptions } = vnode
// 创建实例对象
const instance = {
// 组件的唯一标识
uid: uid++,
// 虚拟节点
vnode,
// 组件类型
type: componentOptions,
// Props 相关
props: {}, // 解析后的 props
attrs: {}, // 未声明的 attrs
propsOptions: componentOptions.props || {}, // 声明的 props 定义
// 状态相关
data: null, // data() 返回的原始数据
state: {}, // 响应式状态(data + setup 合并后)
propsRefs: {}, // props 的响应式引用
// setup 相关
setupContext: null, // setup() 的上下文
setupState: {}, // setup() 返回的状态
// 生命周期钩子
mounted: [], // mounted 钩子列表
beforeMount: [], // beforeMount 钩子列表
beforeUpdate: [], // beforeUpdate 钩子列表
updated: [], // updated 钩子列表
unmounted: [], // unmounted 钩子列表
// 渲染相关
render: null, // 渲染函数
renderCache: [], // 渲染缓存
// 依赖追踪
effects: [], // 关联的 effect
// DOM 相关
el: null, // 挂载的 DOM 元素
vnode: null, // 对应的 vnode
// 组件是否已挂载
isMounted: false,
isUnmounted: false,
// 父组件引用
parent: null,
children: [],
// provide / inject
provides: {},
parentProvides: null
}
return instance
}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
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
处理 props
javascript
/**
* 初始化 props
* 将父组件传递的 props 与组件定义的 props 取交集
*/
function initProps(instance, props) {
const { propsOptions } = instance
// 解析 props
instance.props = resolveProps(propsOptions, props)
// 剩下的放到 attrs
instance.attrs = resolveAttrs(propsOptions, props)
}
/**
* 解析 props 定义
* 只保留组件声明过的 props
*/
function resolveProps(propsOptions, props) {
const resolved = {}
for (const key in props) {
// 如果组件声明了这个 prop
if (key in propsOptions) {
resolved[key] = props[key]
}
}
return resolved
}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
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
处理 setup
setup() 是 Vue3 新增的组合式 API 入口点。
javascript
/**
* 执行 setup() 并处理结果
*/
function setupStatefulComponent(instance) {
const { type: componentOptions } = instance
// 1. 如果有 setup 函数,执行它
if (componentOptions.setup) {
// 创建 setup 上下文
const setupContext = {
attrs: instance.attrs,
emit: (event, ...args) => {
// 实现 emit
const eventName = `on${event.charAt(0).toUpperCase() + event.slice(1)}`
const handler = instance.vnode.props[eventName]
if (handler) {
handler(...args)
}
},
expose: (exposed) => {
// 实现 expose
instance.exposed = exposed
},
slots: instance.slots,
props: instance.props
}
// 执行 setup,传入 props 和上下文
const setupResult = componentOptions.setup(
instance.props,
setupContext
)
// 2. 处理 setup 的返回值
if (typeof setupResult === 'function') {
// setup 返回的是 render 函数
instance.render = setupResult
} else if (typeof setupResult === 'object' && setupResult !== null) {
// setup 返回的是状态对象
instance.setupState = setupResult
}
}
// 3. 如果没有 setup,使用 render 函数
if (!instance.render) {
if (componentOptions.render) {
instance.render = componentOptions.render
} else if (componentOptions.template) {
// 模板编译(后面章节详解)
instance.render = compileTemplate(componentOptions.template)
}
}
}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
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
合并状态
setup() 返回的状态和 data() 返回的状态需要合并:
javascript
/**
* 合并组件状态
* data() 和 setup() 返回的状态合并到 instance.state
*/
function setupState(instance) {
const { data: dataOptions, setupState } = instance
// 创建响应式状态
const state = reactive({})
// 合并 data()
if (dataOptions) {
const data = dataOptions()
Object.keys(data).forEach(key => {
state[key] = data[key]
})
}
// 合并 setup()
if (setupState) {
Object.keys(setupState).forEach(key => {
state[key] = setupState[key]
})
}
// 代理到实例上,支持 this.xxx 访问
instance.proxy = new Proxy(state, {
get(target, key, receiver) {
// 优先从 state 获取
if (key in target) {
return target[key]
}
// 然后从 instance 获取
if (key in instance) {
return instance[key]
}
},
set(target, key, value, receiver) {
if (key in target) {
target[key] = value
return true
}
return false
}
})
return state
}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
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
完整示例
javascript
// 创建一个简单的计数器组件
const Counter = {
// 组件名称(可选)
name: 'Counter',
// props 定义
props: {
initialCount: {
type: Number,
default: 0
}
},
// setup() - 组合式 API 入口
setup(props, { emit, slots, expose }) {
// 使用 ref 创建响应式数据
const count = ref(props.initialCount)
// 暴露方法给父组件
expose({
reset: () => {
count.value = 0
}
})
// 定义方法
function increment() {
count.value++
emit('update', count.value)
}
function decrement() {
count.value--
emit('update', count.value)
}
// 返回的状态和方法可以在模板中使用
return {
count,
increment,
decrement
}
},
// data() - 选项式 API(与 setup 二选一)
// data() {
// return {
// count: this.initialCount
// }
// },
// 渲染函数
render() {
// this 指向组件实例(通过 proxy 访问)
return {
type: 'div',
props: { class: 'counter' },
children: [
{
type: 'span',
children: `计数: ${this.count}` // 通过 this 访问状态
},
{
type: 'button',
props: { onClick: this.increment },
children: '+'
},
{
type: 'button',
props: { onClick: this.decrement },
children: '-'
}
]
}
}
}
// 创建组件实例
const vnode = { type: Counter, props: { initialCount: 5 } }
const instance = createComponentInstance(vnode)
// 初始化
initProps(instance, vnode.props)
setupStatefulComponent(instance)
setupState(instance)
// 此时 instance 结构大致如下:
console.log(instance)
/*
{
uid: 1,
type: Counter,
props: { initialCount: 5 },
setupState: { count: Ref(5), increment, decrement },
data: null,
state: { count: 5, increment, decrement },
proxy: Proxy({ count: 5, ... }),
isMounted: false,
...
}
*/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
101
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
101
实例与 VNode 的关系
VNode(虚拟节点)
├── type: 组件类型定义
├── props: 传递给组件的 props
├── children: 子节点
└── ...其他属性
↓ createComponentInstance
ComponentInstance(组件实例)
├── vnode: 指向创建它的 VNode
├── type: 组件类型定义
├── props: 解析后的 props
├── state: 响应式状态
├── proxy: 访问状态和方法的代理
└── ...生命周期等
↓ renderComponent
真实 DOM1
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
真实源码对照
以下代码来自 Vue3 源码(packages/runtime-core/src/component.ts):
typescript
// packages/runtime-core/src/component.ts
export function createComponentInstance(vnode, parent) {
const instance = {
data: null, // 状态
vnode, // 组件的虚拟节点
subTree: null, // 子树
isMounted: false, // 是否挂载完成
update: null, // 更新的函数
props: {},
attrs: {},
slots: {},
propsOptions: vnode.type.props, // 用户声明的 props 定义
proxy: null, // 代理对象
setupState: {}, // setup() 返回的状态
exposed: null,
parent,
ctx: {},
provides: parent ? parent.provides : Object.create(null),
};
return instance;
}
// 初始化属性:区分 props 和 attrs
const initProps = (instance, rawProps) => {
const props = {};
const attrs = {};
const propsOptions = instance.propsOptions || {};
if (rawProps) {
for (let key in rawProps) {
const value = rawProps[key];
if (key in propsOptions) {
props[key] = value;
} else {
attrs[key] = value;
}
}
}
instance.attrs = attrs;
instance.props = reactive(props);
};
// setup() 执行
export function setupComponent(instance) {
const { vnode } = instance;
initProps(instance, vnode.props);
initSlots(instance, vnode.children);
instance.proxy = new Proxy(instance, handler);
const { data = () => {}, render, setup } = vnode.type;
if (setup) {
const setupContext = {
slots: instance.slots,
attrs: instance.attrs,
expose(value) { instance.exposed = value; },
emit(event, ...payload) {
const eventName = `on${event[0].toUpperCase() + event.slice(1)}`;
const handler = instance.vnode.props[eventName];
handler && handler(...payload);
},
};
setCurrentInstance(instance);
const setupResult = setup(instance.props, setupContext);
unsetCurrentInstance();
if (isFunction(setupResult)) {
instance.render = setupResult;
} else {
instance.setupState = proxyRefs(setupResult);
}
}
if (!isFunction(data)) {
console.warn("data option must be a function");
} else {
instance.data = reactive(data.call(instance.proxy));
}
if (!instance.render) {
instance.render = render;
}
}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
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
总结
| 函数 | 作用 |
|---|---|
createComponentInstance | 创建组件实例对象 |
initProps | 解析和初始化 props |
setupStatefulComponent | 执行 setup() 并处理返回值 |
setupState | 合并 data() 和 setup() 的状态 |
createProxy | 创建实例代理,支持 this.xxx 访问 |