继续上一篇,实现ref和computed
实现shallowReadonly函数
测试用例
// shallowReadonly.spec.ts
import { isReadonly, shallowReadonly } from "../reactive";
describe("shallowReadonly", () => {
it("should not make non-reactive properties reactive", () => {
const props = shallowReadonly({ n: { foo: 1 } });
expect(isReadonly(props)).toBe(true);
expect(isReadonly(props.n)).toBe(false);
});
it("warn then call set ", () => {
console.warn = jest.fn();
const user = shallowReadonly({
age: 10,
});
user.age = 11;
expect(console.warn).toBeCalled();
});
});
代码实现
实现shallowReadonly函数
// reactive.ts
import { mutableHandlers, readonlyHandlers, shallowReadonlyHandlers } from "./basehandler";
export function shallowReadonly(raw){
return createActiveObject(raw,shallowReadonlyHandlers)
}
实现shallowReadonlyHandlers
// basehandler.ts
import { extend } from "../shared";
const shallowReadonlyGet = createGetter(true, true);
// 添加shallow变量 判断是否是shallowReadonly
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key) {
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly;
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly;
}
const res = Reflect.get(target, key);
if (shallow) {
return res;
}
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res);
}
if (!isReadonly) {
track(target, key);
}
return res;
};
}
export const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
get: shallowReadonlyGet,
});
实现isProxy
测试用例
检测是否对象是否是通过reactive或者readonly创造出来的proxy
// reactive.spec.ts
import { isReactive, reactive, isProxy } from "../reactive";
describe("reactive", () => {
it("happy path", () => {
const original = { foo: 1 };
const observed = reactive(original);
expect(observed).not.toBe(original);
expect(observed.foo).toBe(1);
expect(isReactive(observed)).toBe(true);
expect(isReactive(original)).toBe(false);
expect(isProxy(observed)).toBe(true);
});
});
// readonly.spec.ts
import { readonly, isReadonly, isProxy } from "../reactive";
describe("readonly", () => {
it("happy path", () => {
const origin = { foo: 1, bar: { baz: 2 } };
const wrapped = readonly(origin);
// ...省略
expect(isProxy(wrapped)).toBe(true);
});
});
代码实现
// reactive.ts
export function isProxy(value) {
return isReactive(value) || isReadonly(value);
}
实现ref
ref分开三个测试用例来逐步实现
测试用例1及代码实现
// ref.spec.ts
import { effect } from "../effect";
import { ref } from "../ref";
describe("ref", () => {
it("happy path", () => {
const a = ref(1);
expect(a.value).toBe(1);
});
});
对应的代码实现
// ref.ts
import { hasChanged, isObject } from "../shared";
import { isTracking, trackEffects, triggerEffects } from "./effect";
import { reactive } from "./reactive";
class RefImp {
private _value: any;
constructor(value) {
this._value = value;
}
get value() {
return this._value;
}
}
export function ref(value) {
return new RefImp(value);
}
测试用例2及代码实现
// ref.spec.ts
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);
});
对应的代码实现
effect.ts中把收集依赖和触发依赖单独抽离成一个函数
// 收集依赖
const targetMap = new Map();
export function track(target, key) {
if (!isTracking()) return;
// target(代理的对象) → key → dep
// 代理对象的依赖容器
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
// desMap中某个代理对象某个属性所有的依赖
let dep = depsMap.get(key);
if (!dep) {
dep = new Set();
depsMap.set(key, dep);
}
trackEffects(dep);
}
export function isTracking() {
return shouldTrack && activeEffect !== undefined;
}
// 收集依赖的部分单独抽离成一个函数
export function trackEffects(dep) {
if (dep.has(activeEffect)) return;
// 添加依赖
dep.add(activeEffect);
// 反向收集所有依赖到ReactiveEffect实例对象的deps属性上
activeEffect.deps.push(dep);
}
// 触发依赖
export function trigger(target, key) {
let depsMap = targetMap.get(target);
let dep = depsMap.get(key);
triggerEffects(dep);
}
// 触发依赖的部分单独抽离成函数
export function triggerEffects(dep) {
for (const effect of dep) {
if (effect.scheduler) {
effect.scheduler();
} else {
effect.run();
}
}
}
// 最终的effect函数
export function effect(fn, options: any = {}) {
const scheduler = options.scheduler;
const _effect = new ReactiveEffect(fn, scheduler);
extend(_effect, options);
_effect.run();
const runner: any = _effect.run.bind(_effect);
runner.effect = _effect;
return runner;
}
// shared.ts
// 判断两个值是否相等
export function hasChanged(v1, v2) {
return !Object.is(v1, v2);
}
// ref.ts
import { hasChanged, isObject } from "../shared";
import { isTracking, trackEffects, triggerEffects } from "./effect";
import { reactive } from "./reactive";
class RefImp {
private _value: any;
public dep;
constructor(value) {
this._value = value
this.dep = new Set();
}
get value() {
// 收集依赖
trackRefValue(this);
return this._value;
}
set value(newValue) {
// 对比的时候 用原始值和新值比较 不是和proxy对象比较
if (!hasChanged(newValue, this._raw)) return;
this._value = newValue
// 更新完值之后触发依赖
triggerEffects(this.dep);
}
}
function trackRefValue(ref) {
if (isTracking()) {
trackEffects(ref.dep);
}
}
export function ref(value) {
return new RefImp(value);
}
测试用例3及代码实现
// ref.spec.ts
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);
});
对应的实现
import { hasChanged, isObject } from "../shared";
import { isTracking, trackEffects, triggerEffects } from "./effect";
import { reactive } from "./reactive";
class RefImp {
private _value: any;
private _raw: any;
public dep;
constructor(value) {
// 保存没有处理过的原始值
this._raw = value;
// 看看value是不是对象 是的话就用reactive包裹
this._value = convert(value);
this.dep = new Set();
}
get value() {
// 收集依赖
trackRefValue(this);
return this._value;
}
set value(newValue) {
// 对比的时候 用原始值和新值比较 不是和proxy对象比较
if (!hasChanged(newValue, this._raw)) return;
this._raw = newValue;
this._value = convert(newValue);
// 更新完值之后触发依赖
triggerEffects(this.dep);
}
}
function trackRefValue(ref) {
if (isTracking()) {
trackEffects(ref.dep);
}
}
function convert(value) {
return isObject(value) ? reactive(value) : value;
}
export function ref(value) {
return new RefImp(value);
}
实现isRef和unRef
测试用例
// ref.spec.ts
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);
});
it("unRef", () => {
const a = ref(1);
const user = reactive({
age: 1,
});
expect(unRef(a)).toBe(1);
expect(unRef(1)).toBe(1);
});
代码实现
// ref.ts
class RefImp {
private _value: any;
private _raw: any;
public dep;
public __v_isRef = true; // 添加一个属性判断是否是ref对象
// 省略
}
export function isRef(val) {
return !!val.__v_isRef;
}
export function unRef(val) {
return isRef(val) ? val.value : val;
}
实现proxyRef
这个函数可以运用在template中,不用在写.value
测试用例
it("proxyRef",()=>{
const user = {
age:ref(10),
name:"xiaohong"
}
const proxyUser = proxyRef(user)
expect(user.age.value).toBe(10)
expect(proxyUser.age).toBe(10)
expect(proxyUser.name).toBe("xiaohong")
})
代码实现
export function proxyRef(objectWithRefs) {
return new Proxy(objectWithRefs, {
get(target, key) {
return unRef(Reflect.get(target, key));
},
set(target, key, value) {
if (isRef(target[key]) && !isRef(value)) {
return (target[key].value = value);
} else {
return Reflect.set(target, key, value);
}
},
});
}
实现computed
分步骤实现computed
测试用例1及代码实现
// computed.spec.ts
import { computed } from "../computed";
import { reactive } from "../reactive";
describe("computed", () => {
it("happy path", () => {
const user = reactive({
age: 1,
});
const age = computed(() => {
return user.age;
});
expect(age.value).toBe(1)
});
});
对应实现
// computed.ts
class ComputedRef {
private _getter: any;
constructor(getter) {
this._getter = getter;
}
get value() {
return this._getter();
}
}
export function computed(getter) {
return new ComputedRef(getter);
}
测试用例2及代码实现
// computed.spec.ts
it("should compute lazily",()=>{
const value = reactive({
foo:1
})
const getter = jest.fn(()=>{
return value.foo
})
const cValue = computed(getter)
expect(getter).not.toHaveBeenCalled()
expect(cValue.value).toBe(1)
expect(getter).toHaveBeenCalledTimes(1)
// should not compute again
cValue.value
expect(getter).toHaveBeenCalledTimes(1)
// should compute when needed
value.foo = 2
expect(getter).toHaveBeenCalledTimes(1)
expect(cValue.value).toBe(2)
expect(getter).toHaveBeenCalledTimes(2)
// should not compute
cValue.value
expect(getter).toHaveBeenCalledTimes(2)
})
对应代码实现
// computed.ts
import { ReactiveEffect } from "./effect";
class ComputedRef {
private _getter: any;
private _dirty = true;
private _value: any;
private _effect: any;
constructor(getter) {
this._getter = getter;
// scheduler
this._effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true;
}
});
}
get value() {
// 当依赖的响应式对象发生改变时 dirty设置为true
if (this._dirty) {
this._dirty = false;
this._value = this._effect.run();
return this._value;
}
return this._value;
}
}
export function computed(getter) {
return new ComputedRef(getter);
}