Vue3 - 实现 reactive
实现创建响应式对象 API - reactive
使用
- 使用 reactive 创建一个响应式对象
const obj = { name: "jj", age: 20 }
const state = reactive(obj)
核心API - Proxy
- vue3 采用了 ES6 新增的 Proxy 来代理对象实现响应式
下例中:handler 的 get、set 中的 receiver 参数其实就是 proxy 本身
recevier 在代理中起到至关重要的作用
const handler = {
get: function (target, property, receiver) {},
set: function (target, property, value, receiver) {},
}
var p = new Proxy(target, handler) // receiver 是 p
receiver 和 Reflect
MDN 中说明:receiver 是一个Proxy 或者继承 Proxy 的对象
receiver 最主要的作用是用来解决 this 问题,为了方便理解,我们先来看一个例子
let person = {
name: "jj",
get aliasName() {
return "*" + this.name + "*";
},
set aliasName(value) {
this.name = value;
},
};
const proxyPerson = new Proxy(person, {
get(target, key, receiver) { // 取值
return target[key]; // target是 person
},
});
proxyPerson.aliasName; // 只触发了 aliasName 获取操作,没有触发 name 操作
// 页面和数据是有对应关系的 数据变化了要更新视图
proxyPerson.name = "xxx";
上面我们使用 Proxy 代理了 person
- 假如我在视图中使用 aliasName 这个变量,会有 aliasName 对应的页面,但是并没有创造 name 和页面的关系
这意味着,我们执行 proxyPerson.aliasName 时只触发了 aliasName 获取操作,没有触发 name 操作
-
页面和数据是有对应关系的,当数据变化时我们要更新视图
proxyPerson.name = "xxx",此时 name 改变,但是由于没有创造 name 和页面的关系,视图不会更新
为了解决这个问题,这个时候就需要用到 receiver 了,除此之外还用到一个新的 API - Reflect
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法
Reflect.get() 方法,如果
target
对象中指定了get
,receiver
则为get
调用时的`this值
所以 person 的代理改为以下方式
get(target, key, receiver) {
console.log("取值", key);
return Reflect.get(target, key, receiver);
},
处理完后,如果在 get 中 console 一下你会发现打印了两次,一次是读取 aliasName 一次是读取 name
实现 reactive
学习前面的知识后,我们就可以开始实现 reactive 了
function isObject(value) {
return value !== null && typeof value === 'object'
}
const mutableHandlers = {
// 这里的 receiver 就是 下面的 proxy
get(target, key, receiver) {
// 我们在使用 proxy 的时候要搭配 receiver 来使用,用来解决 this 问题
if (isObject(target[key])) {
// 如果取值的时候是对象,再次进行代理,返回代理后的结果
return reactive(target[key])
}
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) { // 更新
return Reflect.set(target, key, value, receiver);
}
}
function reactive(target) {
if (!isObject(target)) return target
// 缓存
// ...
// 如果对象被代理过,说明已经被 proxy 拦截过了
// ...
const proxy = new Proxy(target, mutableHandlers)
return proxy
}
看到这里,是不是觉得 reactive 也就这么回事,其实不然,我们来考虑两个场景:
- 场景一,思考一下打印结果是什么
const obj = { name: "jj", age: 20 };
const state1 = reactive(obj);
const state2 = reactive(obj)
console.log(state1 === state2);
答案是 false,虽然我们代理的对象相同,但是返回的 proxy 是不一样的
- 场景二,代理代理过的对象
const obj = { name: "jj", age: 20 };
const state1 = reactive(obj);
const state3 = reactive(state1)
在 v3 中,已经代理过的对象不会再被代理
- vue3.0 会创建一个反向映射表 { 代理的结果:原内容 }
- 经过迭代去除了反向映射表;如果对象被代理过,说明已经被 proxy 拦截过了,就不再进行代理
代理缓存 - WeakMap
场景一续集
- 在 vue3 中,上面打印结果是 true,原因是 v3 做了一个缓存机制,其实就是一个映射表
- 其中用到了 WeakMap 弱引用
// 缓存
const reactiveMap = new WeakMap()
let existingProxy = reactiveMap.get(target) // 看一下这个对象是否被代理过
if (existingProxy) return existingProxy
ReactiveFlags
场景二续集
- 如果对象被代理过,说明已经被 proxy 拦截过了,就不再进行代理
const enum ReactiveFlags {
IS_REACTIVE = "__v__isReactive"
}
if (target[ReactiveFlags.IS_REACTIVE]) {
return target
}
总结
- reactive 只能处理对象类型的数据
- 代理过的对象会使用映射表进行缓存,防止重复代理
- 已经代理过的对象不会再被代理