期望效果:Vue的计算属性有个特点是仅当依赖变化时才重新计算,不用担心频繁调用时损耗性能。在Vue项目之外也会有要用计算属性的场景。
- 计算属性是个函数,计算结果仅当依赖属性发生变化时才重新计算。
- 计算属性需要收集依赖,并在依赖发生变化时触发更新重新求值。
- 收集依赖可以利用Proxy或Object.defineProperty的get来劫持调用。
- 触发更新可以利用Proxy或Object.defineProperty的set来劫持调用。
先劫持get和set
export const myRef = (value) => {
const obj = { value };
const proxy = new Proxy(obj, {
get(target, key) {
const res = Reflect.get(target, key);
console.log("get", target, key);
return res;
},
set(target, key, value) {
const res = Reflect.set(target, key, value);
console.log("set", target, key);
return res;
},
});
return proxy;
};
- 保存计算属性的getter
export let computedCallback: null | (() => any) = null;
export const myComputed = (getter: () => any) => {
let lastValue;
const update = () => {
console.log("更新前", lastValue);
lastValue = getter();
console.log("更新后", lastValue);
};
computedCallback = update;
update();
computedCallback = null;
return {
get value() {
return lastValue;
},
};
};
编写用来收集依赖和触发依赖的回调函数
interface CallbackMap {
[key: string]: Array<() => any>;
}
export const tracker = (target, key) => {
if (computedCallback && typeof key === "string") {
const callbackMap: CallbackMap = computedMap.get(target) || {};
callbackMap[key] = callbackMap[key] || [];
callbackMap[key].push(computedCallback);
computedMap.set(target, callbackMap);
}
};
export const trigger = (target, key) => {
const callbackMap: CallbackMap | null = computedMap.get(target);
if (callbackMap && typeof key === "string") {
callbackMap[key].forEach((callback) => callback());
}
};
将收集依赖回调放到Proxy的get里,将触发依赖回调放到Proxy的set里
export const myRef = (value) => {
const obj = { value };
const proxy = new Proxy(obj, {
get(target, key) {
const res = Reflect.get(target, key);
console.log("get", target, key);
tracker(target, key);
return res;
},
set(target, key, value) {
const res = Reflect.set(target, key, value);
console.log("set", target, key);
trigger(target, key);
return res;
},
});
return proxy;
};
当依赖属性不是基本类型时
export const myReactive1 = <T extends object>(obj: T) => {
const proxy = new Proxy(obj, {
get(target, key) {
const res = Reflect.get(target, key);
console.log("get", target, key);
tracker(target, key);
return res;
},
set(target, key, value) {
const res = Reflect.set(target, key, value);
console.log("set", target, key);
trigger(target, key);
return res;
},
});
return proxy;
};
当依赖属性的层级较深时需要递归返回Proxy
const myReactiveMap = new WeakMap();
export const myReactive2 = (obj: any) => {
const existProxy = myReactiveMap.get(obj);
if (existProxy) {
return existProxy;
}
const proxy = new Proxy(obj, {
get(target, key) {
const res = Reflect.get(target, key);
console.log("get", target, key);
tracker(target, key);
return res instanceof Object ? myReactive2(res) : res;
},
set(target, key, value) {
const res = Reflect.set(target, key, value);
console.log("set", target, key);
trigger(target, key);
return res;
},
});
myReactiveMap.set(obj, proxy);
return proxy;
};