isProxy
实现 isProxy 非常简单,就我们之前实现的 isReactive 和 isReadonly,只要满足其中之一即为已经代理的对象
it('isProxy', () => { const original = { foo: 1, bar: { baz: 2 } }; const wrapped = readonly(original); const observed = reactive(original); expect(isProxy(wrapped)).toBe(true); expect(isProxy(observed)).toBe(true); }) 复制代码
代码实现
export function isProxy(value: unknown) { return isReadonly(value) || isReactive(value); } 复制代码
ref
ref和 reactive 类似,也是一个实现相应是的 API,区别在于 ref 针对基础类型,reactive 针对的是引用类型,但是其实 ref 也可以传参引用类型,但是其背后还是会转到 reactive 来完成。
收线我们来看一下 ref 的测试用例:
it("should be reactive", () => { const a = ref(1); let dummy; let calls = 0; effect(() => { calls++; dummy = a.value; }); expect(calls).toBe(1); expect(dummy).toBe(1); a.value = 2; expect(calls).toBe(2); expect(dummy).toBe(2); }); 复制代码
被 ref 修饰过的数据需要通过.value
来访问,赋值同样也是;ref也需要进行依赖收集和依赖触发。
然后我们来根据测试用来完成代码,由于之前的依赖收集针对的是多依赖,但是这里 ref 只有一个value, 所有只有一个 dep,不再需要之前的 Map 结构,所以这里对之前的依赖收集重构一下(重构只要要运行测试保证之前的功能不受影响)
export function track<T extends object>(target: T, key: keyof T) { if (!isTracking()) return // target -> key -> dep let depsMap = targetMap.get(target) if (!depsMap) { targetMap.set(target, depsMap = new Map()) } let dep = depsMap.get(key) if (!dep) { depsMap.set(key, dep = new Set<ReactiveEffect>()) } trackEffects(dep) } function trackEffects(dep: Set<ReactiveEffect>) { if (dep.has(activeEffect)) // 如果已经被收集, 中断 return // 收集依赖 dep.add(activeEffect) // 反向收集依赖 activeEffect.deps.push(dep) } 复制代码
同样的一来触发的过程我们也可以抽离出来
export function trigger<T extends object>(target: T, key: keyof T) { let depsMap = targetMap.get(target) let dep = depsMap!.get(key) as Set<ReactiveEffect> trackEffects(dep) } export function triggerEffects(dep: Set<ReactiveEffect>) { for (const effect of dep) { if (effect.scheduler) effect.scheduler() else effect.run() } } 复制代码
由于有了这两个函数的封装,所以这里 ref 的实现也变得非常简单,只需要在 get 时触发 track,set 时触发 trigger 即可
class RefImpl { private _value: any; public dep: Set<ReactiveEffect>; constructor(value: any) { this._value = value; this.dep = new Set(); } get value() { // 在 tracking 阶段收集依赖 if(isTracking()) trackEffects(this.dep) return this._value } set value(newValue) { // 先修改 value 再触发依赖 this._value = newValue; triggerEffects(this.dep) } } export function ref(value: any) { return new RefImpl(value); } 复制代码
此时运行测试,通过
此时并没有结束,我们测试用例中还有一个条件没有写入,当设置重复值的时候不要再次触发 trigger
// ...省略之前的测试代码 // same value should not trigger a.value = 2; expect(calls).toBe(2); expect(dummy).toBe(2); 复制代码
这里也很简单,只需要判断 set 的新值和旧值是否相同即可
set value(newValue) { if (hasChange(newValue, this._value)) return // 先修改 value 再触发依赖 this._value = newValue; triggerEffects(this.dep) } // shared/index.ts export const hasChange = (newValue: any, value: any) => Object.is(newValue, value) 复制代码
上面我们已经说过了,ref 可以借用 reactive 对引用类型进行处理,所以接下来我们完善一下对象类型的调用。
it("should make nested properties reactive", () => { const a = ref({ count: 1, }); let dummy; effect(() => { dummy = a.value.count; }); expect(dummy).toBe(1); a.value.count = 2; expect(dummy).toBe(2); }); 复制代码
然后我们来实现功能,就是在构造函数中判断一下 value 的类型,对不同的类型进行不同的处理即可
constructor(value: any) { this._value = isObject(value) ? reactive(value) : value; this.dep = new Set(); } 复制代码
这里已经实现了代理,但是还有一个问题,就是 set 的时候,如果传入的值是一个对象,那么 this._value的值是一个 proxy 类型,即便是相同的对象在比较是否改变时也会返回 true,所以我们需要在比较时返回代理对象的原对象
class RefImpl { private _value: any; private _rawValue: any; public dep: Set<ReactiveEffect>; constructor(value: any) { this._rawValue = value; this._value = isObject(value) ? reactive(value) : value; this.dep = new Set(); } get value() { // 收集依赖 trackRefValue(this) return this._value } set value(newValue) { if (hasChange(newValue, this._rawValue)) { this._rawValue = newValue; // 先修改 value 再触发依赖 this._value = isObject(newValue) ? reactive(newValue) : newValue; triggerEffects(this.dep) } } } 复制代码
到这里 ref 的功能已经实现了。
isRef
isRef 用于判断目标是否为 ref 响应式对象
it("isRef", () => { const a = ref(1); const user = reactive({ age: 1, }); expect(isRef(a)).toBe(true); expect(isRef(1)).toBe(false); expect(isRef(user)).toBe(false); }); 复制代码
这里判断是否为代理对象的思路和前面的判断 reactive 对象一样,添加一个标识字段即可,只要是使用 ref 代理的都会有这个标识,在判断时只需要返回标识即可。
export function isRef(value: any) { return !!value.__v_isRef } class RefImpl { private _value: any; private _rawValue: any; public dep: Set<ReactiveEffect>; private __v_isRef = true; constructor(value: any) { this._rawValue = value; this._value = convert(value); this.dep = new Set(); } // 省略原来的代码 } 复制代码
unRef
unRef 用于返回被 ref 代理的原始对象
it("unRef", () => { const a = ref(1); expect(unRef(a)).toBe(1); expect(unRef(1)).toBe(1); }); 复制代码
这里的实现也很简单,我们之前的 RefImpl 对象中已经保存了原始value,这里只需要判断是否为 ref 对象然后分别返回对应结果即可。
export function unRef(value: any) { return isRef(value) ? value._rawValue : value }