开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第40天,点击查看活动详情
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
}