什么是响应式数据?其实就是被拦截的对象。
当对象被拦截后,针对对象的各种操作也就能够被拦截下来,从而让我们有机会做一些额外的事情。因此只要是被拦截了对象,就可以看作是一个响应式数据。
在 Vue3 中,创建响应式数据的方式,有 ref 和 reactive 两种,这两个 API 的背后,就是就是针对对象添加拦截。
在 JS 中,要实现数据拦截,要么是 Object.defineProperty,要么是 Proxy,而这两者都是针对对象来进行操作的。
ref 以及 reactive 源码:
class RefImpl<T> {
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v_isRef = true
constructor(
value: T,
public readonly __v_isShallow: boolean,
) {
this._rawValue = __v_isShallow ? value : toRaw(value)
// 有可能是原始值,有可能是 reactive 返回的 proxy
this._value = __v_isShallow ? value : toReactive(value)
}
// 可以把这里的get和set理解为Object.defineProperty
get value() {
// 收集依赖 略
return this._value
}
set value(newVal) {
// 略
}
}
// 判断是否是对象,是对象就用 reactive 来处理,否则返回原始值
export const toReactive = <T extends unknown>(value: T): T =>
isObject(value) ? reactive(value) : value
// 回忆 ref 的用法
const state = ref(5);
state.value;
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>,
) {
// ...
// 创建 Proxy 代理对象
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
)
proxyMap.set(target, proxy)
return proxy
}
export function reactive(target: object) {
// ...
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap,
)
}
从源码中我们就可以看出,ref 和 reactive 在实现响应式上面的策略是有所不同:
- ref:使用 Object.defineProperty + Proxy 方式
- reactive:使用 Proxy 方式
这节课还有一个非常重要的知识点,就是要 学会判断某个操作是否会产生拦截。因为只有产生拦截,才会有后续的依赖收集和派发更新一类的操作。
简单复习上节课的知识,有两个 API 能够实现拦截:
- Object.defineProperty
- 特定的属性的读取
- 特定的属性的赋值
- 操作 Proxy 代理对象的成员
- 读取
- 赋值
- 新增
- 删除
测试题目:
// demo1
let state = ref(1);
state; // 不会拦截
console.log(state); // 不会拦截
console.log(state.value); // 会拦截,因为访问了 value 属性
console.log(state.a); // 不会拦截
state.a = 3; // 不会拦截
state.value = 3; // 会拦截
delete state.value; // 不会拦截
state = 3; // 不会拦截
// demo2
let state = ref({ a: 1 });
state; // 不会拦截
console.log(state); // 不会拦截
console.log(state.value); // 会拦截
console.log(state.a); // 不会拦截
console.log(state.value.a); // 会拦截,拦截到 value 和 a 属性的 get 操作
state.a = 3; // 不会拦截
state.value.a = 3; // 会拦截,value 的 get 操作,a 属性的 set 操作
delete state.value.a; // 会拦截,value 的 get 操作,a 属性的 delete 操作
state.value = 3; // 会拦截,value 的 set 操作
delete state.value; // 不会拦截
state = 3; // 不会拦截
// demo3
let state = reactive({});
state; // 不会拦截
console.log(state); // 不会拦截
console.log(state.a); // 会拦截
state.a = 3; // 会拦截
state.a = {
b: {
c: 3,
},
}; // 会拦截,拦截到 a 属性的 set 操作
console.log("-------------");
console.log(state.a.b.c); // 会拦截
delete state.a.b; // 会拦截 a 是 get 操作,b 是 delete 操作
// demo4
const state = ref({ a: 1 });
const k = state.value;
console.log("-------------");
console.log(k); // 不会拦截,k 相当于是一个 proxy 对象,没有针对成员进行操作
k.a = 3; // 会拦截,因为 k 是一个 proxy 对象,对 k 的成员进行操作会触发代理的 set 操作
const n = k.a; // 会拦截,因为访问了 k 的成员 a,会触发代理的 get 操作
console.log("-------------");
console.log(n);
// demo5
const arr = reactive([1, 2, 3]);
arr; // 不会拦截
arr.length; // 会拦截
arr[0]; // 会拦截,拦截 0 的 get 操作
arr[0] = 3; // 会拦截,拦截 0 的 set 操作
arr.push(4); // 会被拦截