前言
上一篇文章介绍了@vue/composition-api是什么,以及为什么要用,现在来系统地解析一下 @vue/composition-api 的实现原理,希望可以加深对其工作机制的理解。
老规矩先分享下AI评价:对@vue/composition-api实现原理的介绍整体上非常详细和准确,展示了核心代码以及关键逻辑,这为理解其工作机制提供了很好的分析。
核心代码分析
install
引入@vue/composition-api时,需要手动通过Vue.use(),来调用该插件的install,所以我们看看安装时到底做了什么:
export function install(Vue: VueConstructor) {
setVueConstructor(Vue)
mixin(Vue)
}
这里最重要的其实是mixin函数,定义了在beforeCreate钩子中去执行初始化,
构建props和上下文ctx对象,执行setup函数:
export function mixin(Vue: VueConstructor) {
Vue.mixin({
beforeCreate: functionApiInit,
mounted(this: ComponentInstance) {
afterRender(this)
},
beforeUpdate() {
updateVmAttrs(this as ComponentInstance)
},
updated(this: ComponentInstance) {
afterRender(this)
},
})
function functionApiInit(this: ComponentInstance) {
const vm = this
const $options = vm.$options
const {
setup, render } = $options
const {
data } = $options
// wrapper the data option, so we can invoke setup before data get resolved
$options.data = function wrappedData() {
// 在data周期时,进行setup函数的执行
initSetup(vm, vm.$props)
return data || {
}
}
}
function initSetup(vm: ComponentInstance, props: Record<any, any> = {
}) {
const setup = vm.$options.setup!
// 构造ctx对象,有以下key:
// slots: 组件的插槽,默认为 {}
// root: 组件的根实例
// parent: 组件的父实例
// refs: 组件的 ref 引用
// listeners: 组件的事件监听器
// isServer: 是否是服务端渲染
// ssrContext: 服务端渲染的上下文
// emit: 组件的自定义事件触发函数
const ctx = createSetupContext(vm)
const instance = toVue3ComponentInstance(vm)
instance.setupContext = ctx
// 通过Vue.observable对props进行响应式监听
def(props, '__ob__', createObserver())
}
}
ref
ref函数可以把一个普通的值转成响应式的数据。它返回一个可变的ref对象,对象上挂载了一个.value属性,我们可以通过这个.value属性读取或者修改ref的值。
其本质还是基于reactive来实现的响应式,只不过做了一个前置的get、set封装,源码如下:
export function ref(raw?: unknown) {
// ref的本质其实还是调用的reactive
// 然后通过 get/set 方法操作这个对象的值来实现对 ref 值的跟踪和响应
const value = reactive({
[RefKey]: raw })
return createRef({
get: () => value[RefKey] as any,
set: (v) => ((value[RefKey] as any) = v),
})
}
createRef最后还是基于Object.defineProperty:
export function proxy(
target: any,
key: string,
{
get, set }: {
get?: Function; set?: Function }
) {
Object.defineProperty(target, key, {
enumerable: true,
configurable: true,
get: get || noopFn,
set: set || noopFn,
})
}
reactive
底层基于Vue.observable来建立响应式监听,Vue.observable 在 Vue2 和 Vue3 中的实现方式有所区别而已:
export function reactive<T extends object>(obj: T): UnwrapRef<T> {
// 基于Vue.observable
const observed = observe(obj)
setupAccessControl(observed)
return observed as UnwrapRef<T>
}
computed
computed函数用来创建一个计算属性,它根据依赖进行缓存和懒执行。
构建get、set函数,通过vue的Watcher对象,进行监听绑定。
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
): ComputedRef<T> | WritableComputedRef<T> {
const vm = getCurrentScopeVM()
let getter: ComputedGetter<T>
let setter: ComputedSetter<T> | undefined
getter = getterOrOptions.get
setter = getterOrOptions.set
let computedSetter
let computedGetter
const {
Watcher, Dep } = getVueInternalClasses()
let watcher: any
computedGetter = () => {
if (!watcher) {
watcher = new Watcher(vm, getter, noopFn, {
lazy: true })
}
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
computedSetter = (v: T) => {
if (setter) {
setter(v)
}
}
return createRef<T>(
{
get: computedGetter,
set: computedSetter,
},
!setter,
true
) as WritableComputedRef<T> | ComputedRef<T>
}
watch
watch函数用于侦听特定的数据源,并在回调函数中执行副作用。
底层是通过Vue.$watch来实现的:
// 底层是通过Vue.$watch来实现
function createVueWatcher(
vm: ComponentInstance,
getter: () => any,
callback: (n: any, o: any) => any,
options: {
deep: boolean
sync: boolean
immediateInvokeCallback?: boolean
noRun?: boolean
before?: () => void
}
): VueWatcher {
const index = vm._watchers.length
// @ts-ignore: use undocumented options
vm.$watch(getter, callback, {
immediate: options.immediateInvokeCallback,
deep: options.deep,
lazy: options.noRun,
sync: options.sync,
before: options.before,
})
return vm._watchers[index]
}
toRefs
toRefs 可以把一个 reactive 对象的属性都转成 ref 形式。
底层是一个个遍历key去调用toRef,然后基于createRef来保留响应式能力:
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K
): Ref<T[K]> {
if (!(key in object)) set(object, key, undefined)
const v = object[key]
if (isRef<T[K]>(v)) return v
return createRef({
get: () => object[key],
set: (v) => (object[key] = v),
})
}
生命周期
生命周期其实是一个临时覆盖:
// 利用 Vue 的选项合并策略(option merge strategies),
// 通过给组件实例的 $options 注入自定义的生命周期hook函数,来override原有的生命周期选项。
// 生命周期hook函数需要通过 getCurrentInstance() 获取当前活跃的组件实例,
// 并在执行回调前通过 setCurrentInstance()绑定实例,在回调执行完毕后恢复之前的实例。
export const onBeforeMount = createLifeCycle('beforeMount')
export const onMounted = createLifeCycle('mounted')
export const onBeforeUpdate = createLifeCycle('beforeUpdate')
export const onUpdated = createLifeCycle('updated')
export const onBeforeUnmount = createLifeCycle('beforeDestroy')
export const onUnmounted = createLifeCycle('destroyed')
export const onErrorCaptured = createLifeCycle('errorCaptured')
export const onActivated = createLifeCycle('activated')
export const onDeactivated = createLifeCycle('deactivated')
export const onServerPrefetch = createLifeCycle('serverPrefetch')