Vue.js 官网 结论的一处的错误
一、前言
为什么说是对Vue.js官网一处结论描述错误的探究呢?主要是在学习 Composition API 的
setup()
函数发现官网有一段这样的模式
When
setupis executed, the component instance has not been created yet. As a result, you will only be able to access the following properties:
其含义就是:执行
setup
时,组件实例尚未被创建。因此,你只能访问以下 property,下图就是官方对此的描述,真实情况如其所说吗?
二、验证这个错误
正所谓了解真相,方能自由。探索这个问题的对错必须依赖 Vue3源码
- 下载 vue-next 源码
在整个源码中核心文件: runtime-core
、 runtime-dom
、 compiler-core
、 runtime-dom
几个文件
- 进入到 runtime-core 文件夹
- 打开
renderer.ts
文件
我们可以从544行中的这段代码开始一步步分析
2.1 处理组件节点,调用 processComponent
} else if (shapeFlag & ShapeFlags.COMPONENT) {
// 处理组件节点
processComponent(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
}
这里我们来看看 processComponent
的实现中很明显 已经创建了 instance
这么个实例, 接着在1335行中开始调用 setupComponent(instance)
将 上面创建的 instance
传递了出去
2.2 组件挂载,调用 mountComponent
// 组件挂载
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
中进行了处理,那么它又是如何处理呢?
) => {
// 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
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
// 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
函数
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
时,组件实例已经创建
,官方描述是有问题的~