问题
通过本章源码的阅读可以解决以下三个问题:
- 问题一:为什么对props解构后,就变成非响应式的?
- 问题二:传入setup参数中,分别是props和ctx,它们是如何传递进来的?
- 问题三:setup的执行时刻?为什么setup中没有created钩子?
- 问题四:如果setup函数的返回值和data这些数据发生冲突,vue3会如何处理?
源码
我们知道初始化组件时,首次是走processComponent
中的mountComponent
。
createComponentInstance
:instance实例被创建,但是所有api都为nullsetupComponent
:对组件实例的props/slot/data等进行初始化处理(本章主要讲该函数中如何进行初始化)setupRenderEffect
:调用设置和渲染有副作用的函数
// runtime-core/src/renderer
const mountComponent: MountComponentFn = (
initialVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
const compatMountInstance =
__COMPAT__ && initialVNode.isCompatRoot && initialVNode.component
// 1.调用createComponentInstance创建组件实例(创建出来的实例,实例中的属性都是null)
const instance: ComponentInternalInstance =
compatMountInstance ||
(initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
...
// 2.对组件实例的props/slot/data等进行初始化处理
// 对所有数据进行操作和赋值的代码
setupComponent(instance)
...
// 3.调用设置和渲染有副作用的函数
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
)
if (__DEV__) {
popWarningContext()
endMeasure(instance, `mount`)
}
}
setupComponent
:对props和slots进行初始化,并且在setupStatefulComponent
中设置有状态组件
export function setupComponent(
instance: ComponentInternalInstance,
isSSR = false
) {
isInSSRComponentSetup = isSSR
const { props, children } = instance.vnode
// 判断是否是一个有状态的组件
const isStateful = isStatefulComponent(instance)
// 初始化props
initProps(instance, props, isStateful, isSSR)
// 初始化slots
initSlots(instance, children)
// 如果组件有状态,执行状态初始化过程,并返回setup选项的返回值
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)
: undefined
isInSSRComponentSetup = false
return setupResult
}
问题一:为什么对props解构后,就变成非响应式的?
这个问题的答案就在initProps
函数中
export function initProps(
instance: ComponentInternalInstance,
rawProps: Data | null,
isStateful: number, // result of bitwise flag comparison
isSSR = false
) {
const props: Data = {}
const attrs: Data = {}
def(attrs, InternalObjectKey, 1)
instance.propsDefaults = Object.create(null)
setFullProps(instance, rawProps, props, attrs)
...
if (isStateful) {
// stateful
instance.props = isSSR ? props : shallowReactive(props) // props做了浅层响应式
} else {
...
}
instance.attrs = attrs
}
在initProps函数中,会对props做一个浅层响应式,所以对props解构出的值是非响应式的。
setupStatefulComponent
:处理setup,将props和setupContext传给setup函数,最后拿到setup的返回值setupResult,传递到handleSetupResult
中
function setupStatefulComponent(
instance: ComponentInternalInstance,
isSSR: boolean
) {
const Component = instance.type as ComponentOptions
...
instance.accessCache = Object.create(null)
// 创建公共实例/渲染函数代理(通过PublicInstanceProxyHandlers对instance.ctx进行拦截)
instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))
/* setup(props, {attrs, slots, emit, expose}) {
return {
title: "标题"
}
}
*/
// 1.取出setup
const { setup } = Component
if (setup) {
// 2.取出setupContext,{attrs, slots, emit, expose}
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null)
// 在执行setup前,设置当前组件实例对象,所以在setup中可以通过getCurrentInstance获取实例
setCurrentInstance(instance)
pauseTracking()
// 3. 执行setup函数,并且将参数props和{attrs, slots, emit, expose}传递给setup
const setupResult = callWithErrorHandling(
setup,
instance,
ErrorCodes.SETUP_FUNCTION,
[__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
)
resetTracking()
unsetCurrentInstance()
// 对返回值setupResult是promise的处理
if (isPromise(setupResult)) {
setupResult.then(unsetCurrentInstance, unsetCurrentInstance)
...
} else {
// 4.对返回值setupResult进行处理
handleSetupResult(instance, setupResult, isSSR)
}
} else {
finishComponentSetup(instance, isSSR)
}
}
问题二:传入setup参数中,分别是props和ctx,它们是如何传递进来的?
从
setupStatefulComponent
函数中的注释可以看出结论,取出props和setupContext,在callWithErrorHandling
中回调setup函数,并且将props和setupContext作为参数传递给setup
问题三:setup的执行时刻?为什么setup中没有created钩子?
可以看出setup函数执行的时候(在
callWithErrorHandling
中执行),组件实例instance已经创建了,所以setup中处理beforeCreate和created是没有意义的。
问题四:如果setup函数的返回值和data这些数据发生冲突,vue3会如何处理?
本问题的答案在PublicInstanceProxyHandlers
处理器中
export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
get({ _: instance }: ComponentRenderContext, key: string) {
// 取出ctx,setupState,data等等
const { ctx, setupState, data, props, accessCache, type, appContext } =
instance
if (__DEV__ && key === '__isVue') {
return true
}
let normalizedProps
if (key[0] !== '$') {
const n = accessCache![key]
if (n !== undefined) {
switch (n) {
case AccessTypes.SETUP: // 首先从setup中获取
return setupState[key]
case AccessTypes.DATA:// 其次从data中获取
return data[key]
case AccessTypes.CONTEXT:// 再其次ctx中获取
return ctx[key]
case AccessTypes.PROPS: // 最后props中和获取
return props![key]
// default: just fallthrough
}
} else if (hasSetupBinding(setupState, key)) {
accessCache![key] = AccessTypes.SETUP
return setupState[key]
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
accessCache![key] = AccessTypes.DATA
return data[key]
} else if (
...
}
可以看出setup和data共存下,setup优先级会更高。处理方式是对组件实例上下文instance.ctx做代理,在
publicinstanceProxyHandlers
的get中会做逻辑判断处理。
继续向后执行
handleSetupResult
:对setup返回值进行处理
export function handleSetupResult(
instance: ComponentInternalInstance,
setupResult: unknown,
isSSR: boolean
) {
// 如果是函数,那么就作为render函数处理
if (isFunction(setupResult)) {
if (__SSR__ && (instance.type as ComponentOptions).__ssrInlineRender) {
instance.ssrRender = setupResult
} else {
// setupResult将处理成render
instance.render = setupResult as InternalRenderFunction
}
} else if (isObject(setupResult)) {
if (__DEV__ && isVNode(setupResult)) {
warn(
`setup() should not return VNodes directly - ` +
`return a render function instead.`
)
}
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
instance.devtoolsRawSetupState = setupResult
}
// 将来的渲染函数中会首先从setupState中获取值
instance.setupState = proxyRefs(setupResult)
if (__DEV__) {
exposeSetupStateOnRenderContext(instance)
}
} else if (__DEV__ && setupResult !== undefined) {
warn(
`setup() should return an object. Received: ${
setupResult === null ? 'null' : typeof setupResult
}`
)
}
// 最后都会执行finishComponentSetup函数
// 里面主要是对options api做兼容
finishComponentSetup(instance, isSSR)
}
export function finishComponentSetup(
instance: ComponentInternalInstance,
isSSR: boolean,
skipOptions?: boolean
) {
const Component = instance.type as ComponentOptions
...
if (!instance.render) {
if (!isSSR && compile && !Component.render) {
...
// 将template模板编译为render函数
Component.render = compile(template, finalCompilerOptions)
if (__DEV__) {
endMeasure(instance, `compile`)
}
}
}
instance.render = (Component.render || NOOP) as InternalRenderFunction
if (installWithProxy) {
installWithProxy(instance)
}
}
// support for 2.x options
if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {
setCurrentInstance(instance)
pauseTracking()
applyOptions(instance) // 兼容vue2的options api
resetTracking()
unsetCurrentInstance()
}
...