基本的 ref 目标
it('happy path', () => {
const a = ref(1);
expect(a.value).toBe(1);
});
实现
class RefImpl {
private _value: any;
constructor(value) {
this._value = value;
}
get value() {
return this._value;
}
}
export function ref(value) {
return new RefImpl(value);
}
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);
});
实现
class RefImpl {
private _value: any;
// 用于收集依赖的容器
public dep;
constructor(value) {
this._value = value;
this.dep = new Set();
}
get value() {
return this._value;
}
set value(newValue) {
}
}
export function ref(value) {
return new RefImpl(value);
}
会用到 effect 收集依赖的部分,因为 ref 只有一个 value,对 effect 的 track 局部抽离为 trackEffect
同理触发依赖也是对 effect 的 trigger 局部抽离为 triggerEffect
export function track(target, key) {
if (!isTracking()) return;
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let dep = depsMap.get(key);
if (!dep) {
dep = new Set();
depsMap.set(key, dep);
}
// 抽离依赖收集
trackEffect(dep);
}
export function trackEffect(dep) {
// 看盾 dep 之前有没能有添加过,添加过就不添加
if (dep.has(activeEffect)) return;
dep.add(activeEffect);
activeEffect.deps.push(dep);
}
export function trigger(target, key) {
const depsMap = targetMap.get(target);
const dep = depsMap.get(key);
// 抽离触发依赖
triggerEffect(dep);
}
export function triggerEffect(dep) {
for (const effect of dep) {
if (effect.scheduler) {
effect.scheduler();
} else {
effect.run();
}
}
}
export function isTracking() {
return shouldTrack && activeEffect !== undefined;
}
import { trackEffects, triggerEffect } from './effect';
class RefImpl {
private _value: any;
// 用于收集依赖的容器
public dep;
constructor(value) {
this._value = value;
this.dep = new Set();
}
get value() {
// 依赖收集
// 如果没有 effect 过会 trackEffects 中的 activeEffect 会为 undefined
// 这里先作一个判断
if (isTracking()) {
trackEffects(this.dep);
}
return this._value;
}
set value(newValue) {
// 如果 set 的时候,新旧值相同就不作处理
if(Object.is(newValue, this._value)) return;
this._value = newValue;
// 触发依赖
triggerEffects(this.dep);
}
}
重构
set value(newValue) {
// 如果 set 的时候,新旧值相同就不作处理
if(hasChange(newValue, this._value) {
this._value = newValue;
// 触发依赖
triggerEffects(this.dep);
}
}
export const hasChange = (val, newValue) => {
return !Object.is(val, newValue);
};
get value() {
// 依赖收集
trackRefValue(this);
return this._value;
}
function trackRefValue(ref) {
// 如果没有 effect 过会 trackEffects 中的 activeEffect 会为 undefined
// 这里先作一个判断
if (isTracking()) {
trackEffect(ref.dep);
}
}
嵌套目标
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);
});
实现
class RefImpl {
private _value: any;
private _rawValue: any;
public dep;
constructor(value) {
this._rawValue = value;
// 看看 value 是不是对象
this._value = isObject(value) ? reactive(value) : value;
this.dep = new Set();
}
get value() {
trackRefValue(this);
return this._value;
}
set value(newValue) {
// 这里对比会有问题,为因为会转为 proxy,所以要保留未 reactive 的对象
// if (hasChange(newValue, this._value)) {
if (hasChange(newValue, this._rawValue)) {
// 保留未 reactive 的对象
this._rawValue = newValue;
this._value = isObject(newValue) ? reactive(newValue) : newValue;
triggerEffect(this.dep);
}
}
}
重构
抽离 isObject(value) ? reactive(value) : value
class RefImpl {
private _value: any;
private _rawValue: any;
public dep;
constructor(value) {
this._rawValue = value;
// 看看 value 是不是对象
this._value = covert(value);
this.dep = new Set();
}
get value() {
trackRefValue(this);
return this._value;
}
set value(newValue) {
// 这里对比会有问题,为因为会转为 proxy,所以要保留未 reactive 的对象
// if (hasChange(newValue, this._value)) {
if (hasChange(newValue, this._rawValue)) {
// 保留未 reactive 的对象
this._rawValue = newValue;
this._value = covert(newValue);
triggerEffect(this.dep);
}
}
}
function convert(value) {
return isObject(value) ? reactive(value) : value;
}