接上篇解析mount
方法到setupStatefulComponent
方法中,当调用createApp
存在setup
属性时(以本例为模版解析)开始执行setup
方法,本文将详细解析setup
方法,及数据如何设置钩子函数
本例
const { ref, createApp } = Vue
const app = createApp({
// Vue3新增的setup属性
setup(props) {
const message = ref('测试数据')
const modifyMessage = () => {
message.value = '修改后的测试数据'
}
return { message, modifyMessage }
}
}).mount('#app')
setup方法执行
// 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
}
// setupStatefulComponent方法中解析setup位置
const setupResult = callWithErrorHandling(
setup,
instance,
ErrorCodes.SETUP_FUNCTION,
[__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
)
可以看到解析setup
是在callWithErrorHandling
中执行setup
方法,参数是[instance.props, setupContext]
。instance.props
(空对象)是创建instance
对象时生成的,而setupContext
则是根据setup
的长度判读的,本例中为null
。下面就开始执行setup函数。
const message = ref('测试数据')
const modifyMessage = () => {
message.value = '修改后的测试数据'
}
return { message, modifyMessage }
ref函数内部实现(创建RefImpl类的实例对象)
setup
方法内部实现,(依本例来解析)首先定义数据message
,其中执行了ref
方法,ref
方法也是Vue3暴露出来的一个函数,接下来看下ref
内部具体的实现
// ref
export function ref(value?: unknown) {
return createRef(value)
}
// isRef
export function isRef(r: any): r is Ref {
return Boolean(r && r.__v_isRef === true)
}
// createRef
function createRef(rawValue: unknown, shallow = false) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
首先是执行了createRef
函数,其内部首先调用isRef
方法判断数据中是否包含__v_isRef
属性,若包含则直接返回数据,否则new RefImpl()
创建实例,一起看下RefImpl
类的定义。
// RefImpl类
class RefImpl<T> {
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v_isRef = true
constructor(value: T, public readonly _shallow = false) {
this._rawValue = _shallow ? value : toRaw(value)
this._value = _shallow ? value : convert(value)
}
get value() {
trackRefValue(this)
return this._value
}
set value(newVal) {
newVal = this._shallow ? newVal : toRaw(newVal)
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = this._shallow ? newVal : convert(newVal)
triggerRefValue(this, newVal)
}
}
}
// ReactiveFlags.RAW = __v_raw
export function toRaw<T>(observed: T): T {
const raw = observed && (observed as Target)[ReactiveFlags.RAW]
return raw ? toRaw(raw) : observed
}
const convert = <T extends unknown>(val: T): T =>
isObject(val) ? reactive(val) : val
RefImpl
类中首先定义__v_isRef
属性为true
,dep
属性为undefined
,之后执行constructor
传入value
(本例为"测试数据"),设置_rawValue
属性为toRaw(value)
。
toRaw方法是判断数据中是否包含__v_raw属性,存在则递归调用toRaw(value的__v_raw属性值),否则返回value
设置_value的
属性值为convert(value)
convert方法是判断数据是否是引用类型,若是引用类型数据(数组、对象会调用reactive方法再次设置内部的钩子函数,本例为基础数据类型暂不解析reactive方法)
RefImpl类内部钩子函数(依赖收集、运行依赖)
RefImpl
类最后会设置value
的get、set
钩子函数,get
函数是当获取实例对象的value
属性时会触发依赖的收集,并返回实例的_value
属性,也就是创建实例对象时传入的数据;set
钩子替换新的value
值,并运行依赖。
后续如何触发value的钩子、怎样收集依赖,我们在解析生成的render方法时再做详细的分析
回到setup方法本身,数据处理完了,我们来看下方法
const modifyMessage = () => { message.value = '修改后的测试数据' }
可以看到定义修改数据的方法modifyMessage
,观察函数体我们可以发现,是给数据的value
属性设置新值,那么触发set
钩子函数的位置应该是这里。
至于修改数据的方法如何挂到按钮的点击事件上去的,我们等到模版解析完,处理render函数时再仔细分析
最后setup
函数返回了一个对象{ message, modifyMessage }
。至此setup
方法执行完成,返回的对象就是callWithErrorHandling
方法返回的对象。然后我们回归到setupStatefulComponent
方法中
Proxy代理setup返回的数据对象
if (isPromise(setupResult)) {
// ...
} else {
handleSetupResult(instance, setupResult, isSSR)
}
本例中返回的setupResult
非Promise
对象,所以执行handleSetupResult
方法
export function handleSetupResult(
instance: ComponentInternalInstance,
setupResult: unknown,
isSSR: boolean
) {
if (isFunction(setupResult)) {}
else if (isObject(setupResult)) {
// ...
instance.setupState = proxyRefs(setupResult)
if (__DEV__) {
exposeSetupStateOnRenderContext(instance)
}
// ...
} else if (__DEV__ && setupResult !== undefined) {}
finishComponentSetup(instance, isSSR)
}
// proxyRefs
export function proxyRefs<T extends object>(
objectWithRefs: T
): ShallowUnwrapRef<T> {
return isReactive(objectWithRefs)
? objectWithRefs
: new Proxy(objectWithRefs, shallowUnwrapHandlers)
}
//
const shallowUnwrapHandlers: ProxyHandler<any> = {
get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),
set: (target, key, value, receiver) => {
// ...
}
}
可以看到handleSetupResult
方法的内部首先执行proxyRefs
,目的是通过Proxy
代理setup
函数的返回结果,为其属性设置set、get
钩子函数,并赋值给instance.setupState
。之后再执行exposeSetupStateOnRenderContext
方法。
instance.ctx新增Proxy对象属性
export function exposeSetupStateOnRenderContext(
instance: ComponentInternalInstance
) {
const { ctx, setupState } = instance
Object.keys(toRaw(setupState)).forEach(key => {
if (!setupState.__isScriptSetup && (key[0] === '$' || key[0] === '_')) {
// ... console.warn()
return
}
Object.defineProperty(ctx, key, {
enumerable: true,
configurable: true,
get: () => setupState[key], // ctx.xxx = setupState['xxx']
set: NOOP
})
})
}
此函数作用主要是将setupState
对象上的属性通过Object.defineProperty
赋值给ctx
对象
那么整个初始化流程就串连起来了,当我们执行生成的
render
方法时,根据with(ctx)
的特性,获取ctx.message
,则会触发setupState.message
的获取,触发setupState
的get
钩子函数,实际上get
钩子函数执行的unref
方法其实就是获取RefImpl
实例对象的value
属性值,那么就会触发RefImpl
实例对象的get
钩子函数,从而获取数据然后收集依赖
回到handleSetupResult
方法最后,执行了finishComponentSetup
函数,在完成setup
的解析之后,开始进行template
模版编译。
总结
[以本例为模版解析] 执行setup
函数,内部定义会使用到ref
(或者reactive
方法,是Vue3
暴露对外的),本质是创建RefImpl
的实例化对象,并增加get
(收集依赖)、set
(运行依赖)钩子函数。setup
函数返回的对象通过Proxy
代理并赋值给instance
的setupState
属性(为render
函数解析时遇到相应的属性名而触发钩子函数做准备)。最后将setupState
对象的属性值通过Object.defineProperty
赋值给ctx
对象(在解析render
函数时会使用到)。后续将解析template
模版,看是如何生成render
函数的。