Vue3 使用 Proxy 对象重写响应式系统。相比 Vue2有以下不同:
- 多层属性嵌套,在访问属性过程中才会处理下一级属性(响应式性能提升)
- 默认监听动态添加的属性
- 默认监听属性的删除操作
- 默认监听数组索引和length属性
- 可以作为单独的模块使用
响应式系统关键API:
- reactive: 响应式处理对象或数组
- ref:响应式处理基本类型数据
- toRefs:将proxy代理的对象的所有属性值,都变成响应式数据
reactive
- 参数只能是对象或数组 (简单类似使用ref)
- 修改属性,响应式。重新赋值,不是响应式,需要再次使用 reactive 处理
- 得到的响应式对象不能解构(解构用toRefs)
/**
* 响应式处理对象或数组
* 1. 判断参数,如果不是对象或数组直接返回。
* 2. proxy 代理 target。
* get 收集依赖,如果属性值是对象,需要递归处理。返回处理后的属性值。
* set 属性值变化,触发更新。如果新属性值是对象,需递归处理。返回boolean。
* deleteProperty 对象属性存在,成功删除后,触发更新。返回boolean。
* @param { object, array } target
* @return { proxy }
*/
const isObject = (val) => val !== null && typeof val === "object";
const isArray = (val) =>
Object.prototype.toString.call(val) === "[object Array]";
const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
const convert = (target) => (isObject(target) ? reactive(target) : target);
export function reactive(target) {
if (!isObject(target)) {
return target;
}
const handler = {
get(target, key, receiver) {
// 收集依赖
track(target, key);
const result = Reflect.get(target, key, receiver);
return convert(result);
},
set(target, key, value, receiver) {
const oldValue = Reflect.get(target, key, receiver);
if (value === oldValue) return true;
const result = Reflect.set(target, key, convert(value), receiver);
// 触发更新
trigger(target, key);
return result;
},
deleteProperty(target, key, receiver) {
const hasKey = hasOwn(target, key);
const result = Reflect.deleteProperty(target, key, receiver);
if (hasKey && result) {
// 触发更新
trigger(target, key);
console.log("delete", key);
}
return result;
},
};
return new Proxy(target, handler);
}
/**
* watchEffect 底层调用的方法
* 在callback() 访问响应式对象属性,收集依赖
* @param { function } callback
*/
let activeEffect = null;
export function effect(callback) {
activeEffect = callback;
callback();
activeEffect = null;
}
/**
* 收集依赖
* @param {object, array} target
* @param {string} key
*/
let targetMap = new WeakMap();
export function track(target, key) {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let dep = depsMap.get(key);
if (!dep) {
dep = new Set();
depsMap.set(key, dep);
}
dep.add(activeEffect);
}
/**
* 触发更新
* 先根据target、key找到回调集合,遍历执行。
* @param {object, array} target
* @param {string} key
*/
export function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (!dep) return;
dep.forEach((cb) => {
cb();
});
}
ref
- 参数为基础类型,内部创建具有value属性的对象,value属性具有getter和setter。
- 参数为对象,就相当于reactive(obj) 返回代理对象
- 返回的对象,重新赋值成对象,也是响应式的。
/**
* 对原始数据类型进行响应式处理
* 1. 如果是带有__v_isRef的对象,直接返回
* 2. 使用convert对raw进行递归的reactive处理,得到value
* 3. 返回的对象,__v_isRef = true,value属性 获取会收集依赖,设置会触发更新。
* @param {string, boolean, number} raw
* @return {object}
*/
export function ref(raw) {
if (isObject(raw) && row.__v_isRef) return;
let value = convert(raw);
const r = {
__v_isRef: true,
get value() {
track(r, "value");
return value;
},
set value(newValue) {
if (newValue !== value) {
value = convert(newValue);
trigger(r, "value");
}
},
};
return r;
}
toRefs
- 参数为 proxy代理的对象
- 将proxy代理的对象的所有属性值,都变成响应式数据。可解构。
/**
* 将响应式对象所有属性,变成ref
* @param {*} proxy
*/
export function toRefs(proxy) {
const result = isArray(proxy) ? [] : {};
for (let key in proxy) {
result[key] = toRef(proxy, key);
}
return result;
}
/**
* 将 响应式对象 指定的属性,变成ref
* 1. 返回的对象解构与ref返回的类似
* 2. 无需收集依赖和触发更新,是因为 proxy 本来是响应式的
* @param {proxy} proxy proxy实例
* @param {string} key
*/
export function toRef(proxy, key) {
return {
__v_isRef: true,
get value() {
return proxy[key];
},
set value(newValue) {
proxy[key] = newValue;
},
};
}