构建reactive函数,获取proxy实例
整个reactive函数,本质是返回了一个proxy实例,我们先去实现这个reactive函数,得到proxy实例;
1.创建packages/reactivity/src/reactive.ts模块:
reactive.ts
import { mutableHandlers } from "./baseHandlers";
export const reactiveMap = new WeakMap<object, any>();
export function reactive(target: object) {
return createReactiveObject(target, mutableHandlers, reactiveMap);
}
function createReactiveObject(
target: object,
baseHandlers: ProxyHandler<any>,
proxyMap: WeakMap<object, any>
) {
const existingProxy = proxyMap.get(target);
// 已经被代理了直接返回
if (existingProxy) {
return existingProxy;
}
// 没有被代理则生成proxy实例
const proxy = new Proxy(target, baseHandlers);
// 缓存proxy
proxyMap.set(target, proxy);
return proxy;
}
2.创建packages/reactivity/src/baseHandlers.ts模块;
baseHandlers.ts
export const mutableHandlers: ProxyHandler<object> = {};
3.创建packages/reactivity/src/index.ts;
index.ts
export { reactive } from "./reactive";
5.创建packages/vue/src/index.ts;
index.ts
export { reactive } from "@vue/reactivity";
在终端执行npm run build;生成dist目录下的文件,此时我们的reactive函数就被成功导出了;
接下来我们去创建对应的测试实例;创建packages/vue/examples/reactivity/reactive.html;
reactive.html
// 导入刚才打的包
<script src="../../dist/vue.js"></script>
<script>
const { reactive } = Vue;
const obj = reactive({
name: "张三",
});
</script>
运行之后打印:
实现createGetter和createSetter方法
在packages/reactivity/src/baseHandlers.ts中修改:
baseHandlers.ts
import { track, trigger } from "./effect";
const get = createGetter();
const set = createSetter();
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
};
function createGetter() {
return function get(target: object, key: string | symbol, receiver: object) {
const res = Reflect.get(target, key, receiver);
// 依赖收集
track(target, key);
return res;
};
}
function createSetter() {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
) {
const result = Reflect.set(target, key, value, receiver);
// 触发依赖
trigger(target, key, value);
return result;
};
}
新建packages/reactivity/src/effect.ts
effect.ts
/**
* 收集依赖
* @param target
* @param key
*/
export function track(target: object, key: unknown) {
console.log("收集依赖");
}
/**
* 触发依赖
* @param target
* @param key
* @param newValue
*/
export function trigger(target: object, key: unknown, newValue: unknown) {
console.log("触发依赖");
}
接下来测试一下,是否会触发track和trigger方法;记得要重新build;
js
<script>
const { reactive } = Vue;
const obj = reactive({
name: "张三",
});
console.log("obj--->", obj.name);
obj.name = "李四";
</script>
track和trigger方法被成功打印出来了;
小插曲:给项目加上热更新
给package.json新加一条指令;
package.json
"scripts": {
"dev": "rollup -c -w",
}
构建effect函数,生成ReactiveEffect实例
创建好reactive实例之后,接下来我们需要触发effect;
js
effect(() => {
document.querySelector("#app").innerText = obj.name;
});
在effect中,生成了ReactiveEffect实例,并且触发了getter;接下来实现effect方法;
1.在packages/reactivity/src/effect.ts中,创建effect方法;
effect.ts
export function effect<T = any>(fn: () => T) {
const _effect = new ReactiveEffect(fn);
_effect.run();
}
export let activeEffect: ReactiveEffect | undefined;
export class ReactiveEffect<T = any> {
constructor(public fn: () => T) {}
run() {
activeEffect = this;
return this.fn();
}
}
2.导出effect方法;
去修改我们的测试实例;
js
<script>
const { reactive, effect } = Vue;
const obj = reactive({
name: "张三",
});
effect(() => {
document.querySelector("#app").innerText = obj.name;
});
</script>
此时页面中展示出来了张三
;
实现track和trigger方法
依赖收集和依赖触发原理
- track用来依赖收集;
- trigger用来依赖触发;
响应性是:当响应性数据触发setter时执行fn函数;如果想要实现这个目的,那么必须在getter时去收集当前的fn函数,以便在setter时可以执行对应的fn函数;
reactive.ts
export const reactiveMap = new WeakMap<object, any>();
WeakMap的key必须是一个对象,并且key是一个弱引用的;举个例子
js
const obj = reactive({
name: "张三",
});
effect(() => {
document.querySelector("#app").innerText = obj.name;
});
1.WeakMap:
1.key:响应性对象 (此时为obj)
2.value:Map对象
1.key:响应性对象的指定属性 (name)
2.value:指定对象的指定属性的执行函数 (fn函数)
实现track依赖收集函数
依赖收集的本质就是实现activeEffect和targetMap的绑定:
effect.ts
type KeyToDepMap = Map<any, ReactiveEffect>;
const targetMap = new WeakMap<any, KeyToDepMap>();
/**
* 收集依赖
* @param target
* @param key
*/
export function track(target: object, key: unknown) {
// activeEffect是fn函数
if (!activeEffect) return;
console.log("收集依赖");
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
depsMap.set(key, activeEffect);
console.log("targetMap--->", activeEffect, targetMap);
}
此时targetMap的值为:
实现trigger触发依赖函数
所有的依赖关系都保存到了targetMap之中,所以依赖触发也就是从targetMap中读取对应的effect,然后执行对应的fn函数;
effect.ts
/**
* 触发依赖
* @param target
* @param key
* @param newValue
*/
export function trigger(target: object, key: unknown, newValue: unknown) {
console.log("触发依赖");
const depsMap = targetMap.get(target);
if (!depsMap) {
return;
}
const effects = depsMap.get(key) as ReactiveEffect;
if (!effects) {
return;
}
effects.fn();
}
修改一个测试实例;
js
<script>
const { reactive, effect } = Vue;
const obj = reactive({
name: "张三",
});
effect(() => {
document.querySelector("#app").innerText = obj.name;
});
setTimeout(() => {
obj.name = "李四";
}, 4000);
</script>