实现ref功能
什么是ref?
ref其实和reactive一样,都是创建响应式数据的方法。只不过ref是一个单值,只能用 .value 去获取或者修改。
首先,还是老样子,测试文件
1、第一个测试,是创建一个ref数据,并取出。
2、用effect去监听ref,ref类型修改赋值之前相同的值,还会返回之前没修改时候的值。
3、用ref去创建响应式对象,需要处理哪些边缘case。
ref.spec.ts
import { effect } from "../src/effect";
import { reactive } from "../src/reactive";
import { ref } from "../src/ref";
describe("ref", () => {
it("happy path", () => {
const a = ref(1);
expect(a.value).toBe(1);
});
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++;
expect(calls).toBe(2);
expect(dummy).toBe(2);
// same value should not trigger
a.value = 2;
expect(calls).toBe(2);
expect(dummy).toBe(2);
});
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);
});
});
接下来,实现ref.ts
因为调用的时候都是用 .value 去获取,所以ref也是一个类的实现
ref.ts
import { hasChange, isObject } from "../../shared";
import { isTracking, trackEffects, triggerEffects } from "./effect";
import { reactive } from "./reactive";
class RefImpl {
private _value: any;
public dep: Set<RefImpl>;
// 缓存最原始的数据,因为可能接收到单一类型或者对象类型
private _rawValue: any;
constructor(value) {
this._rawValue = value;
// 如果是对象类型,需要封装成 reactive,这里抽离出转化通用方法
this._value = convert(value);
// 进行dep 初始化
this.dep = new Set();
}
get value() {
// 收集依赖
trackRefValue(this);
return this._value;
}
set value(newValue) {
// hasChange 发生改变才会收集依赖并且赋值;具体用 Object.is 取反实现
// 用新值和之前原始数据比较
if (hasChange(newValue, this._rawValue)) {
// 赋值
this._rawValue = newValue;
this._value = convert(newValue);
// 触发依赖
triggerEffects(this.dep);
}
}
}
function trackRefValue(ref) {
// 和reactive一样,如果没用effect去监听,就不需要依赖收集,否则会找不到当前的effect实例,即 activeEffect 为 undefined
if (isTracking()) {
// 收集依赖,因为ref是单一值 .value 所以直接收集dep即可
trackEffects(ref.dep);
}
}
function convert(value) {
return isObject(value) ? reactive(value) : value;
}
export function ref(value) {
return new RefImpl(value);
}
shared 通用方法
index.ts
...
export const hasChange = (val, newVal) => {
return !Object.is(val, newVal);
};
以上,执行测试,通过。
最后
附上git代码地址:mini-vue,欢迎大家踊跃探讨,如果可以的话,帮忙点点 git star,万分感谢!