跳到主要内容

模拟实现

2023年06月11日
柏拉文
越努力,越幸运

一、packages/reactivity/src/index.ts

export { effect } from './effect';
export { reactive } from './reactive';

二、packages/reactivity/src/reactive.ts


import { mutableHandlers } from './baseHandlers';

export const reactiveMap = new WeakMap<WeakMap<object, any>>();

export function reactive(target) {
return createReacttiveObject(target, mutableHandlers, reactiveMap);
}

function createReacttiveObject(
target: object,
baseHandlers: ProxyHandler<any>,
proxyMap: WeakMap<object, any>
) {
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
const proxy = new Proxy(target, baseHandlers);
proxyMap.set(target, proxy);
return proxy;
}

三、packages/reactivity/src/baseHandlers.ts


import { track, trigger } from './effect';

function createGetter() {
return function get(target: object, key: string | symbol, reaceiver: object) {
const result = Reflect.get(target, key, reaceiver);
track(target, key);
return result;
};
}

function createSetter() {
return function set(
target: object,
key: string | symbol,
value: any,
receiver: object
) {
const result = Reflect.set(target, key, value, receiver);
trigger(target, key, value);
return result;
};
}

const get = createGetter();
const set = createSetter();

export const mutableHandlers: ProxyHandler<object> = {
get,
set
};

四、packages/reactivity/src/effect.ts


import { Dep, createDep } from './dep';

type KeyToDepMap = Map<any, Dep>;
const targetMap = new WeakMap<any, KeyToDepMap>();

export let activeEffect: ReactiveEffect | undefined;

export class ReactiveEffect<T = any> {
fn: () => T;
constructor(fn: () => T) {
this.fn = fn;
}
run() {
activeEffect = this;
return this.fn();
}
}

export function trackEffects(dep: Dep) {
dep.add(activeEffect as ReactiveEffect);
console.log(targetMap);
}

export function track(target: object, key: any) {
if (!activeEffect) {
return;
}
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}

let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = createDep()));
}
trackEffects(dep);
}

export function triggerEffect(effect: ReactiveEffect) {
effect.run();
}

export function triggerEffects(deps: Dep) {
const effects = Array.isArray(deps) ? deps : [...deps];
for (const effect of effects) {
triggerEffect(effect);
}
}

export function trigger(target: object, key: any, value: any) {
const depsMap = targetMap.get(target);
if (!depsMap) {
return;
}
const deps: Dep | undefined = depsMap.get(key) as Dep;
if (!deps) {
return;
}
triggerEffects(deps);
}

export function effect<T = any>(fn: () => T) {
const _effect = new ReactiveEffect(fn);
_effect.run();
}

五、packages/reactivity/src/dep.ts


import { ReactiveEffect } from './effect';

export type Dep = Set<ReactiveEffect>;

export const createDep = (effects?: ReactiveEffect[]): Dep => {
const dep = new Set<ReactiveEffect>(effects);
return dep;
};

六、测试用例


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue3-Next-Mini Reactive 测试用例</title>
<script src="../packages/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<p id="p1"></p>
<p id="p2"></p>
</div>
<script>
const { effect, reactive } = Vue;

const obj = {
a: 1,
b: 2
};
const objProxy = reactive(obj);

effect(() => {
document.querySelector('#p1').innerHTML = objProxy.a;
});

effect(() => {
document.querySelector('#p2').innerHTML = objProxy.a;
});

setTimeout(() => {
objProxy.a = '1修改';
}, 3000);
</script>

<script>
const object = {
a: 1,
b: 2
};
const proxy = new Proxy(object, {
get(target, key, receiver) {
console.log('访问');
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log('设置');
return Reflect.set(target, key, value, receiver);
}
});

console.log(proxy.a);
const { a } = proxy;
console.log(a);
console.log(a);
console.log(a);
</script>
</body>
</html>