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

结合Vue3-demo项目分析源码(三),完成了组件的初始化,认识了各种类型的组件、浅层响应式对象即全局api。回到方法setupStatefulComponent()继续组件的创建。

分析

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

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
function setupStatefulComponent(
instance: ComponentInternalInstance,
isSSR: boolean
) {
const Component = instance.type as ComponentOptions
// 1. 创建当前组件实例的代理对象
instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
// 2. 如果组件提供了 setup 方法
const { setup } = Component
if (setup) {
// setup 的上下文对象
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null)

currentInstance = instance
pauseTracking()
// 调用 setup 方法,获取返回值 setupResult
const setupResult = callWithErrorHandling(
setup,
instance,
ErrorCodes.SETUP_FUNCTION,
[__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
)
resetTracking()
currentInstance = null
// 处理 setup 方法返回值
if (isPromise(setupResult)) {
if (isSSR) {
// return the promise so server-renderer can wait on it
return setupResult.then((resolvedResult: unknown) => {
handleSetupResult(instance, resolvedResult, isSSR)
})
} else if (__FEATURE_SUSPENSE__) {
// 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.`
)
}
} else {
// 返回值为非 promise 对象的类型时候,调用 handleSetupResult 方法处理返回值
handleSetupResult(instance, setupResult, isSSR)
}
} else {
// 完成组件的创建
finishComponentSetup(instance, isSSR)
}
}

setup

setup方法为Vue3提供的新方法,为了定义聚合式(组合式)api组件

  • 组合式api:一种低侵入式、函数式的API。无需指定一长串选项来定义组件,允许用户像编写函数一样自由组合逻辑和代码,可以更灵活的组合组件的逻辑。

方法setup是组合式api的入口,在beforeCreate钩子之前调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//使用setup前
created() {
console.log(this.$props);
console.log(" this 就是组件实例的代理对象 proxy");
// 调用全局的方法
this.$showSuccuessDialog("我是全局的 showSuccuessDialog 方法");
},
//使用setup后
setup(props,setupContext){
const instance = getCurrentInstance();
console.log(props)
console.log("instance为组件实例的代理对象proxy,即先前的this")
console.log(instance.proxy);//即先前的this
//调用全局方法
instance.proxy.$showSuccessDialog("全局showSuccessDialog方法");
}

setup参数

  • props: 组件的属性值

  • setupContext: setup方法的上下文对象

对象setupContext定义可查看文件vue-next/packages/runtime-core/src/component.ts第751行

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
export function createSetupContext(
instance: ComponentInternalInstance
): SetupContext {
const expose: SetupContext['expose'] = exposed => {
if (__DEV__ && instance.exposed) {
warn(`expose() should be called only once per setup().`)
}
instance.exposed = proxyRefs(exposed)
}

if (__DEV__) {
// We use getters in dev in case libs like test-utils overwrite instance
// properties (overwrites should not be done in prod)
return Object.freeze({
get props() {
return instance.props
},
get attrs() {
return new Proxy(instance.attrs, attrHandlers)
},
get slots() {
return shallowReadonly(instance.slots)
},
get emit() {
return (event: string, ...args: any[]) => instance.emit(event, ...args)
},
expose
})
} else {
return {
attrs: instance.attrs,
slots: instance.slots,
emit: instance.emit,
expose
}
}
}

由此可得,setupContext对象有四个属性

  • attrs:除props之外的属性值

  • slots:组件插槽

  • emit:事件触发函数

  • expose:设置暴露在ref上的对象

handleSetupResult()

处理setup方法返回的结果

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

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
export function handleSetupResult(
instance: ComponentInternalInstance,
setupResult: unknown,
isSSR: boolean
) {
//若返回值为方法
if (isFunction(setupResult)) {
// setup returned an inline render function
if (__NODE_JS__ && (instance.type as ComponentOptions).__ssrInlineRender) {
// when the function's name is `ssrRender` (compiled by SFC inline mode),
// set it as ssrRender instead.
instance.ssrRender = setupResult
} else {
//返回的不是ssr函数,则把返回的方法给instance的render属性
instance.render = setupResult as InternalRenderFunction
}
//返回值是对象
} else if (isObject(setupResult)) {
...
//直接将返回的对象给instance的setupState属性
instance.setupState = proxyRefs(setupResult)
...
} else if (__DEV__ && setupResult !== undefined) {
warn(
`setup() should return an object. Received: ${
setupResult === null ? 'null' : typeof setupResult
}`
)
}
//完成组件setup
finishComponentSetup(instance, isSSR)
}

解读

  1. 返回值为方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
setup(props,setupContext){
const instance = getCurrentInstance();
console.log(props)
console.log("instance为组件实例的代理对象proxy,即先前的this")
console.log(instance.proxy);//即先前的this
//调用全局方法
instance.proxy.$showSuccessDialog("全局showSuccessDialog方法");
onMounted(()=>{
//获取暴露在hello组件ref对象的name属性
console.log(instance.proxy.$refs["refHello"].name);
});
//setup返回值为方法
return () => h("div","setup返回了一个方法");
}

handleSetupResult方法显示,直接传递给instancerender方法,给页面不显示原来template内容,而直接被渲染为返回值方法的内容

返回方法

  1. 返回值为对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
setup(props,setupContext){
const instance = getCurrentInstance();
console.log(props)
console.log("instance为组件实例的代理对象proxy,即先前的this")
console.log(instance.proxy);//即先前的this
//调用全局方法
instance.proxy.$showSuccessDialog("全局showSuccessDialog方法");
onMounted(()=>{
//获取暴露在hello组件ref对象的name属性
console.log(instance.proxy.$refs["refHello"].name);
});
//setup返回值为方法
// return () => h("div","setup返回了一个方法");
//返回值为对象
return {
setupMsg: "setup返回了一个对象,我是属性setupMsg"
}
}

handleSetupResult方法显示,setup返回的对象直接传递给instancesetupState属性,按方法setupStatefulComponent,当用proxy获取instance属性时,首先从setupState中取。

2iJxN8.png

finishComponentSetup()

当方法handleSetupResult处理完结果,直接调用finishComponentSetup方法完成组件的创建。

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

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
function finishComponentSetup(
instance: ComponentInternalInstance,
isSSR: boolean
) {
const Component = instance.type as ComponentOptions

// template / render function normalization
if (__NODE_JS__ && isSSR) {
//若组件有render方法,则直接将其赋予组件实例
if (Component.render) {
instance.render = Component.render as InternalRenderFunction
}
} else if (!instance.render) {
// could be set from setup()
//若组件提供了template属性,则调用compile方法将它转换为render函数
if (compile && Component.template && !Component.render) {
if (__DEV__) {
startMeasure(instance, `compile`)
}
Component.render = compile(Component.template, {
isCustomElement: instance.appContext.config.isCustomElement,
delimiters: Component.delimiters
})
if (__DEV__) {
endMeasure(instance, `compile`)
}
}

instance.render = (Component.render || NOOP) as InternalRenderFunction
...
}

//兼容Vue2组件参数
// support for 2.x options
if (__FEATURE_OPTIONS_API__) {
currentInstance = instance
pauseTracking()
applyOptions(instance, Component)
resetTracking()
currentInstance = null
}

...
}

一个组件最后都会有一个render方法,前面用setup返回了一个方法充当了组件的render方法。

由上述方法得知,可直接使用sfc组件的形式通过vue-loader编译template,或给组件提供template属性,由compile方法编译成render方法,或直接提供一个render方法。

applyOptions()

为了兼容Vue2组件调用此方法处理Vue2一些属性的兼容。

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

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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
export function applyOptions(
instance: ComponentInternalInstance,
options: ComponentOptions,
deferredData: DataFn[] = [],
deferredWatch: ComponentWatchOptions[] = [],
deferredProvide: (Data | Function)[] = [],
asMixin: boolean = false
) {
...
// applyOptions is called non-as-mixin once per instance
//开始调用beforeCreate生命周期
if (!asMixin) {
isInBeforeCreate = true
callSyncHook(
'beforeCreate',
LifecycleHooks.BEFORE_CREATE,
options,
instance,
globalMixins
)
isInBeforeCreate = false
// global mixins are applied first
//应用全局的mixins
applyMixins(
instance,
globalMixins,
deferredData,
deferredWatch,
deferredProvide
)
}

// extending a base component...
//处理extends属性
if (extendsOptions) {
applyOptions(
instance,
extendsOptions,
deferredData,
deferredWatch,
deferredProvide,
true
)
}
// local mixins
//处理本地mixins
if (mixins) {
applyMixins(instance, mixins, deferredData, deferredWatch, deferredProvide)
}
...

//处理inject属性
if (injectOptions) {
...
}
//处理methods属性
if (methods) {
...
}
//处理data属性
if (!asMixin) {
if (deferredData.length) {
deferredData.forEach(dataFn => resolveData(instance, dataFn, publicThis))
}
if (dataOptions) {
resolveData(instance, dataOptions, publicThis)
}
...
} else if (dataOptions) {
deferredData.push(dataOptions as DataFn)
}
//处理computed属性
if (computedOptions) {
...
}
//处理watch属性
if (watchOptions) {
deferredWatch.push(watchOptions)
}
...

//处理provide属性
if (provideOptions) {
deferredProvide.push(provideOptions)
}
...

// asset options.
// To reduce memory usage, only components with mixins or extends will have
// resolved asset registry attached to instance.
//处理components
if (asMixin) {
if (components) {
...
}
//处理directives
if (directives) {
...
}
}

// lifecycle options
//调用created生命周期
if (!asMixin) {
callSyncHook(
'created',
LifecycleHooks.CREATED,
options,
instance,
globalMixins
)
}
//处理beforeMount、mount等生命周期
if (beforeMount) {
onBeforeMount(beforeMount.bind(publicThis))
}
if (mounted) {
onMounted(mounted.bind(publicThis))
}
...
//兼容Vue2的expose属性
if (isArray(expose)) {
if (!asMixin) {
if (expose.length) {
const exposed = instance.exposed || (instance.exposed = proxyRefs({}))
expose.forEach(key => {
exposed[key] = toRef(publicThis, key as any)
})
} else if (!instance.exposed) {
instance.exposed = EMPTY_OBJ
}
} else if (__DEV__) {
warn(`The \`expose\` option is ignored when used in mixins.`)
}
}
}

方法主要处理Vue2的属性的兼容处理,在处理之前调用了组件的生命周期beforeCreate方法,处理后调用了组件的created方法。

beforeCreate生命周期

由上述方法applyOptions可得,在beforeCreated方法中无法访问datacomputedmethodswatch等属性,只能访问propsattrssetupState等内容。

created生命周期

当组件完成初始化并设置完成后,组件才算创建完成,调用created生命周期提示“组件已经创建完成”。此时可以访问除渲染过程才有的其他所有属性。

一些概念

  • mixin:混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2020-2024 Aweso Lynn
  • PV: UV: