1、响应式系统介绍
-
Proxy 对象实现属性监听
-
多层属性嵌套,在访问属性过程中处理下一级属性
-
默认监听动态添加的属性
-
默认监听属性的删除操作
-
默认监听数组索引和 length 属性
-
可以作为单独的模块引用
核心方法:
- reactive / ref / toRefs / computed
- effect
- track
- trigger
2、Proxy对象基本使用
const jiayin = new Proxy(data, {
// target 目标对象
// property 被获取的属性名
// receiver Proxy或者继承Proxy的对象
get(target, key, receiver) {
console.log("get---", key, receiver);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log("set---", key, value, receiver);
return Reflect.set(target, key, receiver); // 在严格模式下,若set方法返回false,则会抛出一个 TypeError 异常。
},
deleteProperty(target, key) {
console.log("delete---", target, key);
return Reflect.deleteProperty(target, key);
},
});
-
set
和deleteProperty
需要返回布尔类型的值,严格模式下,如果返回 false 的话会出现 Type Error 的异常; -
如果 handler 没有设置任何拦截,那就等同于直接通向原对象
var proxy = new Proxy({}, {})
; -
关于 Reflect,一开始有疑问,为什么不直接在 Proxy 内获取到值,非要多此一举使用 Reflect。后来我的理解是,Reflect 对象的方法与 proxy 对象的方法一一对应,且 Reflect 对象用起来更好,所以采用,并不是 proxy 的用不了,也不知道这个理解是否正确。
-
Proxy 和 Reflect 中使用的 receiver
const obj = { name: "kkk", get foo() { console.log("this-->", this); // 这个this指向receiver return this.bar; }, }; const proxy = new Proxy(obj, { get(target, key, receiver) { if (key === "bar") { return "value - bar"; } return Reflect.get(target, key, receiver); }, }); console.log(proxy.foo); // value - bar
3、reactive实现
前置知识:如何判断对象是否有某个属性
(1)点( . )或者方括号( [ ] )
(2)in 运算符
(3)Reflect.has(target, propertyKey)
(4)hasOwnProperty
总结:
最好使用 Object.prototype.hasOwnProperty.call(foo, "bar")
, 这样可以保证用的是当前对象原型上的 hasOwnProperty 方法,不会使用自身的了。
vue3中如果使用 Reflect.has(target, key)
会导致问题,就是 delete obj.hasOwnProperty
的时候,尽管target对象没有 hasOwnProperty 这个属性,也会执行
代码实现
-
新建一个 reactive 函数,接收参数 data
-
几个工具函数:
- 判断 data 是否为对象
const isObject = value => typeof value === 'object' || value === null
- 嵌套对象,判断是否需要递归
const convert = target => isObject(target) ? reactive(target) : target
- 判断对象中是否拥有该属性
const hasOwn = (target, key) => Object.prototype.hasOwnProperty.call(target, key)
- 判断 data 是否为对象
-
创建拦截器对象 handler, 设置 get / set / deleteProperty
-
返回 Proxy 对象
// 判断是否是对象
const isObject = (value) => typeof value === "object" || value === null;
// 如果对象是嵌套状态,就递归处理
const convert = (target) => (isObject(target) ? reactive(target) : target);
// 判断对象中是否有该属性
// const hasOwnProperty = (target, key) => target.hasOwnProperty(key) // 这个方法并不好
const hasOwn = (target, key) =>
Object.prototype.hasOwnProperty.call(target, key);
export const reactive = (data) => {
if (!isObject(data)) return data;
const handler = {
get(target, key, receiver) {
// 收集依赖
console.log("get--", key);
const res = Reflect.get(target, key, receiver);
return convert(res);
},
set(target, key, value, receiver) {
const oldValue = Reflect.get(target, key, receiver); // 获取旧值与新值对比
let res = true; // 定义返回的布尔变量
if (oldValue !== value) {
console.log("set--", key);
res = Reflect.set(target, key, value, receiver);
// 触发更新
}
return res;
},
deleteProperty(target, key) {
const hasKey = hasOwn(target, key);
const res = Reflect.deleteProperty(target, key);
if (hasKey && res) {
// 触发更新
console.log("delete---", key);
}
return res;
},
};
return new Proxy(data, handler);
};
4、ref 实现
前置知识:js 中的 get、set 方法
/*
- obj中的name是数据属性
- get、set后的age属性是访问器属性
访问器属性:当外部js给age赋值时走的时setter函数,当外部js获取age时 走的getter函数,setter和getter是隐藏函数,会取我们写在age后边的函数
*/
let obj = {
name: 1,
get age() {
return 22;
},
set age(value) {
console.log("set");
obj.name = value;
},
};
代码实现
-
判断传入的是否是 ref 创建的对象,如果是直接返回;
-
传入的不是 ref 创建的对象,内部调用 reactive 创建响应式数据;
-
最后创建一个有 value 属性的对象,value属性的值为第二步处理后的值;
// ... 这里的代码接 reactive 节的代码 export const ref = (raw) => { // 1. 判断 raw 是否是 ref 创建的对象,如果是,直接返回 if(isObject(raw) && raw.__v_isRef) // 2. 传入的是普通数据,进行处理 // 普通对象 -> 被 reactive 处理为响应式对象 // 基本数据 -> 直接返回为基本数据 let reactiveData = convert(raw) // 3. 创建一个有 value 属性的对象返回 const r = { __v_isRef: true, // 注意,这个一定要有 get value() { console.log('ref--get-->>>', reactiveData); // todo:收集依赖 return reactiveData }, set value(newValue) { console.log('ref--set-->>>', reactiveData, newValue); if(newValue !== reactiveData) { raw = newValue reactiveData = convert(raw) // todo:触发更新 } } } return r }
5、reactive vs ref
6、toRefs
- toRefs 函数接收一个 reactive 返回的响应式对象,也就是 Proxy 对象
- 把传入的对象的所有属性转成一个类似 ref 返回的对象,注意对 array 和其他数据类型的处理
- 把转换后后的对象挂载到一个新的对象上返回
export const toRefs = (proxy) => {
// 这一步不好理解,对对象和其他数据类型的处理
let ret = proxy instanceof Array ? new Array(proxy.length) : {}
for (const key in proxy) {
ret[key] = toProxyRef(proxy, key)
}
return ret
}
// 将属性转成类似 ref 返回的对象
const toProxyRef = (proxy, key) => {
let r = {
__v_isRef: true,
get value() {
// 注意:这里不需要数据劫持和更新,因为本来就是响应式的数据了,不需要劫持
return proxy[key]
},
set value(newValue) {
proxy[key] = newValue
}
}
return r
}
7、computed
computed内部使用的是effect实现(还未完全明白)
export const computed = (getter) => {
const result = ref()
effect(() => (result.value = getter()))
return result
}