reactive 通过 proxy 进行 get set 操作实现响应式。但是 reactive 只作用于对象。那么像 1,true,'1'这类的单值怎么实现响应式呢?
ref
想实现这些单值的响应式,本质上也是要去思考怎么实现 get set 操作
class RefImpl {
get value() {
...
}
set value() {
...
}
}
从上面的代码可以看出 用这个class包裹一个 get set,从而实现依赖收集和依赖触发,接下来我们去实现一下 ref 这个功能
happy path 单元测试
descreibe("ref", () => {
it("happy path", () => {
const a = ref(1);
expect(a.value).toBe(1);
});
})
从以上单元测试可知,用 ref 包裹1 赋值给 a,通过 a.value 得到 1
代码实现
class RefImpl {
private _value: any;
constructor(value) {
this._value = value;
}
get value() {
return this._value;
}
}
export function ref(value) {
return new RefImpl(value);
}
响应式 单元测试
descreibe("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);
// same value should not trigger
a.value = 2;
expect(calls).toBe(2);
expect(dummy).toBe(2);
});
})
从以上单元测试可知,当 a 的值发生变化,dummy 和 calls 的值也发生变化。当把一个重复的值赋值给 a,不进行触发依赖
代码实现
class RefImpl {
private _value: any;
public dep; // 存放依赖的容器
constructor(value) {
this._value = value;
this.dep = new Set();
}
get value() {
// 判断是否存在 activeEffect
trackRefValue(this)
return this._value;
}
set value(newValue) {
if (hasChanged(this._value, newValue)) {
this._value = newValue;
triggerEffects(this.dep);
}
}
}
function trackRefValue(ref) {
if (isTracking()) {
trackEffects(ref.dep);
}
}
export function ref(value) {
return new RefImpl(value);
}
对 effect.ts 中 dep 收集和触发暴露出来给 ref 调用
// effect.ts
export function trackEffects(dep) {
// 依赖已经存在 dep 中就不再添加
if (dep.has(activeEffect)) return;
dep.add(activeEffect);
activeEffect.deps.push(dep);
}
export function triggerEffects(dep) {
for (let effect of dep) {
if (effect.scheduler) {
effect.scheduler();
} else {
effect.run();
}
}
}
export function isTracking() {
return shouldTrack && activeEffect !== undefined;
}
公共方法调用
// shared index.ts
export const isObject = (val) => {
return val !== null && typeof val === "object";
};
export const hasChanged = (val, newVal) => {
return !Object.is(val, newVal);
};
ref 嵌套 reactive 单元测试
descreibe("ref", () => {
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++;
expect(dummy).toBe(2);
});
})
代码实现
class RefImpl {
private _value: any;
private _rawValue: any; // 用于 set 时候和新值做对比
public dep; // 存放依赖
constructor(value) {
this._rawValue = value;
this._value = convert(value);
this.dep = new Set();
}
get value() {
// activeEffect 存在,才执行依赖收集
trackRefValue(this);
return this._value;
}
set value(newValue) {
if (hasChanged(this._rawValue, newValue)) {
this._rawValue = newValue;
this._value = convert(newValue);
triggerEffects(this.dep);
}
}
}
function convert(value) {
return isObject(value) ? reactive(value) : value;
}
function trackRefValue(ref) {
if (isTracking()) {
trackEffects(ref.dep);
}
}
export function ref(value) {
return new RefImpl(value);
}
这里声明一个 _rawValue 是为了当 value 被 reactive 包裹后是一个 proxy,而 newValue 是一个普通的对象