前言
上一篇《Vue 3 Pre-Alpha / vue-next 源码学习之 vue/reactivity/reactive.ts》我们学习了使用 Proxy 实现的 reactive 构造出来的响应式的对象,但 reactive 接受的值的参数限于 Object, Array, Map, Set, WeakMap, WeakSet 这些类型,而本篇介绍的 ref 与 reactive 也类似,但可以接收任何类型的值的参数。而且对值的监听的实现方式也不一样,使用 get set 的方式对值实现拦截监听,然后在 get 操作时添加 track,在 set 操作时调用 trigger,从而达到响应式的效果。
源码分析
注意带【】符号的为关键代码,关键代码能体现作者的最关键最基础的实现思路
import { track, trigger } from './effect' // track 了之后就会把 effect add 到 dep 中,然后再把 dep push 到 effect.deps里,trigger 顾名思义就是触发相关的所有 effects 事件的执行
import { OperationTypes } from './operations' // set, add, delete, clear, get, has, iterate 操作类型
import { isObject } from '@vue/shared'
import { reactive } from './reactive' // 上篇介绍的 reactive 构造函数,用于生成一个被 proxy 的代理对象
import { ComputedRef } from './computed' // 顾名思义,应该就是 与 Vue 2.x computed 类似的 Ref
export interface Ref<T = any> { // 把所有值转化成一个 Object 类形,其值藏于解套 UnwrapRef 后的 value 中
_isRef: true
value: UnwrapRef<T>
}
const convert = (val: any): any => (isObject(val) ? reactive(val) : val) // 如果值为 Object,就直接转成 Reactive 对象
export function ref<T extends Ref>(raw: T): T
export function ref<T>(raw: T): Ref<T>
export function ref(raw: any) { // ref 构造函数
if (isRef(raw)) {
return raw
}
raw = convert(raw)
const v = { // 【Ref 的关键代码】:把 raw 值使用 get set 转化成一个 watchable 的 Object
_isRef: true,
get value() { // get 操作需要 track
track(v, OperationTypes.GET, '')
return raw
},
set value(newVal) { // set 操作需要 trigger
raw = convert(newVal)
trigger(v, OperationTypes.SET, '')
}
}
return v as Ref
}
export function isRef(v: any): v is Ref { // 是否为 Ref 类型呢?v._isRef === true 就是
return v ? v._isRef === true : false
}
export function toRefs<T extends object>( // 把 object (K, T[K]) 对像转化成另外一个 key 相同为 K 但值为 Ref<T[K]> 的 Object
object: T
): { [K in keyof T]: Ref<T[K]> } {
const ret: any = {}
for (const key in object) {
ret[key] = toProxyRef(object, key)
}
return ret
}
function toProxyRef<T extends object, K extends keyof T>( // 把 T[K] 变成 Ref<T[K]>
object: T,
key: K
): Ref<T[K]> {
return {
_isRef: true,
get value(): any {
return object[key]
},
set value(newVal) {
object[key] = newVal
}
}
}
type BailTypes =
| Function
| Map<any, any>
| Set<any>
| WeakMap<any, any>
| WeakSet<any>
// Recursively unwraps nested value bindings.
export type UnwrapRef<T> = { // 神奇的递归解套,UnwrapRef({a: {b: {c: 1}}}}) => {1删object: {2删a : {3删object: {4删b: {5删object: {6删c: {7删ref: 1}}}}}}} => 1
cRef: T extends ComputedRef<infer V> ? UnwrapRef<V> : T
ref: T extends Ref<infer V> ? UnwrapRef<V> : T
array: T extends Array<infer V> ? Array<UnwrapRef<V>> : T
object: { [K in keyof T]: UnwrapRef<T[K]> }
}[T extends ComputedRef<any>
? 'cRef'
: T extends Ref
? 'ref'
: T extends Array<any>
? 'array'
: T extends BailTypes
? 'ref' // bail out on types that shouldn't be unwrapped
: T extends object ? 'object' : 'ref']
总结
要实现数据的响应式的绑定,实现的思路有很多,如 Vue 2.x 使用的是 Object.definedProperty 的方式。而到了 Vue 3 则大胆使用 ES6 的 Proxy(reactive 的实现) 及 get set(Ref的实现)。还有不得不说,本篇源码中的 typescript 的神奇的递归解套真是非常精彩。真是应了那句:Talk is cheap, show me the code!