coderz blog

执行 setup 时,组件实例真的如官方所说未创建?

2021-06-18

Vue.js 官网 结论的一处的错误

一、前言

为什么说是对Vue.js官网一处结论描述错误的探究呢?主要是在学习 Composition API 的 setup()函数发现官网有一段这样的模式

Whensetup is executed, the component instance has not been created yet. As a result, you will only be able to access the following properties:

其含义就是:执行 setup 时,组件实例尚未被创建。因此,你只能访问以下 property,下图就是官方对此的描述,真实情况如其所说吗?

截屏2021-06-18 下午4.30.39.png

二、验证这个错误

正所谓了解真相,方能自由。探索这个问题的对错必须依赖 Vue3源码

  • 下载 vue-next 源码

在整个源码中核心文件: runtime-core runtime-dom compiler-core runtime-dom 几个文件

  • 进入到 runtime-core 文件夹
  • 打开 renderer.ts文件

我们可以从544行中的这段代码开始一步步分析

2.1 处理组件节点,调用 processComponent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
} else if (shapeFlag & ShapeFlags.COMPONENT) {          
// 处理组件节点
processComponent(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
}

这里我们来看看 processComponent 的实现中很明显 已经创建了 instance 这么个实例, 接着在1335行中开始调用 setupComponent(instance) 将 上面创建的 instance 传递了出去

2.2 组件挂载,调用 mountComponent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  // 组件挂载
const mountComponent: MountComponentFn = (
initialVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
// 2.x compat may pre-creaate the component instance before actually
// mounting
const compatMountInstance = __COMPAT__ && initialVNode.component
// 在这里创建了 ComponentInternalInstance
const instance: ComponentInternalInstance =
compatMountInstance ||
(initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
2.2 开始处理 setupComponent

该函数在 component.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
) => {
// 2.x compat may pre-creaate the component instance before actually
// mounting
const compatMountInstance = __COMPAT__ && initialVNode.component
const instance: ComponentInternalInstance =
compatMountInstance ||(initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
if (__DEV__ && instance.type.__hmrId) {
registerHMR(instance)
}
if (__DEV__) {
pushWarningContext(initialVNode)
startMeasure(instance, `mount`)
}

// inject renderer internals for keepAlive
if (isKeepAlive(initialVNode)) {
;(instance.ctx as KeepAliveContext).renderer = internals
}

// resolve props and slots for setup context
if (!(__COMPAT__ && compatMountInstance)) {
if (__DEV__) {
startMeasure(instance, `init`)
}
// 开始调用 setup,将 instance 实例传递进入
setupComponent(instance)
if (__DEV__) {
endMeasure(instance, `init`)
}
}

调转到它的实现文件中,我们可以看到这样一段代码实现,在这个函数中处理几件事情

  • 调用 setupStatefulComponent 初始化有状态的组件
  • 初始化 props slots
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export function setupComponent(
instance: ComponentInternalInstance,
isSSR = false
) {
isInSSRComponentSetup = isSSR

const { props, children } = instance.vnode
const isStateful = isStatefulComponent(instance)
initProps(instance, props, isStateful, isSSR)
initSlots(instance, children)

const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)
: undefined
isInSSRComponentSetup = false
return setupResult
}
2.3 提取 setup函数

在这里通过 const { setup } = Component拿到 该函数,看到这段代码也就能得出一个结论

  • 如果实现了 setup函数,那么通过vue2 options api 是不会生效的
  • vue3是兼容 vue2 options api
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
// 2. call setup()
// 在这里开始处理 setup()函数
const { setup } = Component
if (setup) {
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null)

currentInstance = instance
pauseTracking()
const setupResult = callWithErrorHandling(
setup,
instance,
ErrorCodes.SETUP_FUNCTION,
[__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
)
resetTracking()
currentInstance = null

if (isPromise(setupResult)) {
if (isSSR) {
// return the promise so server-renderer can wait on it
return setupResult
.then((resolvedResult: unknown) => {
handleSetupResult(instance, resolvedResult, isSSR)
})
.catch(e => {
handleError(e, instance, ErrorCodes.SETUP_FUNCTION)
})
} 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 {
handleSetupResult(instance, setupResult, isSSR)
}
} else {
finishComponentSetup(instance, isSSR)
}
2.4 统一执行 callWithErrorHandling 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
export function callWithErrorHandling(
fn: Function,
instance: ComponentInternalInstance | null,
type: ErrorTypes,
args?: unknown[]
) {
let res
try {
res = args ? fn(...args) : fn()
} catch (err) {
handleError(err, instance, type)
}
return res
}

这个函数其实本质就是自己封装了一个 try catch ,在其内部来来调 fn

三、结论

  • 结合源码一分析,就很确定 执行 setup 时,组件实例 已经创建,官方描述是有问题的~
Tags: Vue
使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章