16_实现相对完善的reactive
一、我们要优化哪些点?
既然要实现相对完善的reactive
,那自然需要考虑的多一点。
那我们大概列举一些边缘case
的简单考虑:
reactive
的参数类型问题,如果传入的参数不是对象呢?- 如果
reactive
的对象已经是一个响应式对象了呢,如何处理?也就是嵌套问题:reactive(reactive(obj))
。 - 那自然还有另外一个类似的问题:同一个对象,分别两次调用
reactive
后拿到的响应式对象还是同一个吗?
看着是不是有些头大,但是这些都是我们应该要考虑到的地方。
不着急,接下来一个个来完善。
二、reactive相关考虑完善
(一)参数类型问题
1. 单测用例
it('reactive params type must be object', () => {
console.warn = jest.fn();
// 传入的不是一个对象
const original = reactive(1);
expect(console.warn).toBeCalled();
expect(original).toBe(1);
});
2. 完善逻辑
众所周知,proxy
不能代理基本数据类型,所以遇到基本数据类型,我们应该直接返回原数据,并给一个提示。
那第一步,就得判断是不是对象,而且这应该是一个工具函数,所以,封装进shared
。
// src/shared/index.ts
export const isObject = (val) => {
return val !== null && typeof val === 'object';
};
工具函数完成,那我们只需要在reactive
中对入参进行判断即可。
// src/reactivity/reactive.ts
function createReactiveObject(target: any, baseHandlers) {
// + 不是对象,警告,返回原数据
if (!isObject(target)) {
console.log(`value cannot be made reactive: ${ String(target) }`);
return target;
}
return new Proxy(target, baseHandlers);
}
3. 单测结果

(二)多层嵌套问题
1. 单测用例
it('observing already observed value should return same Proxy', () => {
const original = { foo: 1 };
const observed = reactive(original);
const observed2 = reactive(observed);
expect(observed2).toBe(observed);
});
2. 完善逻辑
核心逻辑:
我们只需要判断target
是否是响应式对象,是的话,则返回target
,否则就按正常逻辑来。
实现思路:
仔细一看,是不是可以类比isReactive
和isReadonly
的实现,这样的话,那就简单了。
我们为ReactiveFlags
增加一个枚举RAW
,值为__v_raw
。
然后当嵌套时,判断target
是否有ReactiveFlags[RAW]
。 如果已经是响应式对象,则在createGetter
中判断读取是否key
为ReactiveFlags[RAW]
,是的话,则返回target
,中断reactive
。 那如果不是响应式对象呢,那自然就没有这个属性,继续往下走好了。
代码实现:
// src/reavtivity/reactive.ts
export const enum ReactiveFlags {
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly',
RAW = '__v_raw'
}
function createReactiveObject(target: any, baseHandlers, proxyMap) {
if (!isObject(target)) {
console.warn(`value cannot be made reactive: ${ String(target) }`);
return target;
}
if (target[ReactiveFlags.RAW]) {
return target;
}
return new Proxy(target, baseHandlers);
}
// src/reactivity/baseHandlers.ts
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;
} else if (key === ReactiveFlags.RAW) {
// + 增加判断
return target;
}
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;
};
}
3. 单测结果

(三)多次监测问题
1. 单测用例
it('observing the same value multiple times should return same Proxy', () => {
const original = { foo: 1 };
const observed = reactive(original);
const observed2 = reactive(original);
expect(observed2).toBe(observed);
});
2. 完善逻辑
当我们多次监测同一个对象时,我们拿到的应该也是同一个响应式对象。
一方面是为了性能问题,避免多次代理造成的性能浪费;
另一方面,也是为了避免一些由此引起的不必要的问题。
就举个简单的例子:
例如在依赖收集track
时,我们是根据原始对象的target
和key
去一一对应收集依赖进targetMap
的。 此处注意是原始对象!
那么就就意味着:如果同一对象,多次代理可以返回不同的响应式对象时,那么这些响应式对象对应的依赖将会收集在同一个targetMap
中。
由此带来的就是:只要其中一个响应式对象发生变化,那么所有的这些响应式对象对应的依赖都会被触发并更新,而且我们也并没有必要去增加另外的一些标识,来区分这些由同一个对象产生的不同代理,那将会增加很多心智负担,那并不是我们所期望的。
实现思路:
我们只需要将已经代理过的对象和对应的代理,按照原始对象target
→ 响应式对象proxy
一对一存储即可。并且由于是弱引用,所以我们采用WeakMap
来存储。
代码实现:
// src/reactivity/reactive.ts
export const reactiveMap = new WeakMap();
export const readonlyMap = new WeakMap();
export const shallowReadonlyMap = new WeakMap();
function createReactiveObject(target: any, baseHandlers, proxyMap) {
if (!isObject(target)) {
console.warn(`value cannot be made reactive: ${ String(target) }`);
return target;
}
if (target[ReactiveFlags.RAW]) {
return target;
}
const existingProxy = proxyMap.get(target);
// + 这里解决的是reactive多层嵌套的问题
if (existingProxy) {
return existingProxy;
}
const proxy = new Proxy(target, baseHandlers);
// + 缓存一下已经被代理的对象
proxyMap.set(target, proxy);
return proxy;
}
export function reactive(target) {
return createReactiveObject(target, mutableHandlers, reactiveMap);
}
export function readonly(target) {
return createReactiveObject(target, readonlyHandlers, readonlyMap);
}
export function shallowReadonly(target) {
return createReactiveObject(target, shallowReadonlyHandlers, shallowReadonlyMap);
}
3. 单测结果
