创建一个完善的vue3响应式系统
vue
重要的特性就是响应式,这次使用ES6
的proxy
特性,手动实现一个响应式,深入理解vue3响应式原理
副作用函数
副作用函数
: 是指会产生副作用的函数,直接或间接的影响外部参数产生变化
var num = 1
function fns() {
num = 2 // 修改全局变量,产生副作用
}
fns()
console.log(num) // 2
实现一个简单的响应式功能
const obj = { text: "hello world" };
function effect() {
document.body.innerHTML = obj.text
}
effect()
obj.text = 'hello vue3'
- 如上述代码,当副作用函数执行后,页面显示
hello world
,如果再次给obj.data
重新赋值,不能触发副作用函数直接修改,没有响应式的执行。
如果想要设置成响应式,就要监听它的读取和设置操作
1.当读取时,会把当前的副作用函数存储在一个'桶'中
2.在重新赋值操作中,会将存储的副作用函数取出再重新执行
在
vue2
中,实现响应式的是ES5的Object.defineProperty
而在vue3
中,实现响应式的是ES6的代理对象proxy
来实现
基本思路:
// 存储副作用函数的桶
const bucket = new Set();
// 原始数据
const data = { text: "hello world" };
// 对原始数据的代理
const obj = new Proxy(data, {
// 拦截读取操作
get(target, key) {
// 将副作用函数 effect 添加到存储副作用函数的桶中
bucket.add(effect);
// 返回属性值
return target[key];
},
// 拦截设置操作
set(target, key, newVal) {
// 设置属性值
target[key] = newVal;
// 把副作用函数从桶里取出并执行
bucket.forEach((fn) => fn());
// 返回 true 代表设置操作成功
return true;
},
});
- 使用一个定时器可以验证当重新改变参数时,会再次触发副作用函数执行
function effect() {
document.body.innerHTML = obj.text
}
effect()
setTimeout(() => {
obj.text = 'hello vue3'
},1000)
一个完善的响应式系统
现在一个基本响应式系统创建好了,但是还有一些问题:
- 当前的副作用函数写死了,如果副作用函数不是'effect'时,就不能正确实现响应式
- 解决方法:
提供一个用来注册副作用函数的机制
- 解决方法:
// 用一个全局变量存储被注册的副作用函数
let activeEffect;
// effect 函数用于注册副作用函数
function effect(fn) {
activeEffect = fn;
// 执行副作用函数
fn();
}
// 参数是一个匿名的副作用函数
effect(() => {
document.body.innerHTML = obj.text;
});
修改代理对象中的get
的存储方式
get(target, key) {
// bucket.add(effect); 之前的
// 将 activeEffect 中存储的副作用函数收集到“桶”中
if (activeEffect) { // 新增
bucket.add(activeEffect) // 新增
} // 新增
return target[key]
}
- 如果定时器中添加一个不存在的参数,并且没有与副作用函数建立联系,但还是会触发了响应式监听
- 根本原因:
没有在副作用函数与被操作的字段之间建立明确的关系
,无论是读取的哪一个属性,都会当前桶中存储的副作用函数取出并执行
- 根本原因:
effect(() => {
console.log('执行副作用函数'); // 测试可以会执行两次
document.body.innerHTML = obj.text;
});
setTimeout(() => {
// obj.text = "heeeeeeeeee";
obj.noText = '不存在'
}, 1000);
- 解决方法:
副作用函数与被被操作的字段之间建立联系
,将桶改成Map
,将参数和副作用函数挂钩起来
基本思路:
// 创建一个新桶来存储副作用函数,包含key和value
const bucket = new WeakMap();
const obj = new Proxy(data, {
get(target, key) { // target:当前对象,key:触发监听的key
// 没有正在执行的副作用函数 直接返回
if (!activeEffect) return target[key];
// 从这个桶中取出一个Map类型(key -> value)
let depsMap = bucket.get(target);
// 不存在,则创建一个Map与target关联
if (!depsMap) {
bucket.set(target, (depsMap = new Map()));
}
// 根据key判断每个key上是否存在对应的副作用函数
let deps = depsMap.get(key);
// 不存在,则新建一个new Set,并与key关联
if (!deps) {
depsMap.set(key, (deps = new Set()));
}
// 最后将当前激活的副作用函数添加到桶中
deps.add(activeEffect);
return target[key];
// 将 activeEffect 中存储的副作用函数收集到“桶”中
// if(activeEffect) {
// bucket.add(activeEffect)
// }
},
set(target, key, newVal) {
target[key] = newVal;
// 根据target从桶中取得depsMap,它是key --> effects
const depsMap = bucket.get(target)
if(!depsMap) return
// 根据key取得当前对应的副作用函数
const effects = depsMap.get(key)
// 执行副作用函数
effects && effects.forEach(fn => fn())
// // 把副作用函数从桶里取出并执行
// bucket.forEach((fn) => fn());
// // 返回 true 代表设置操作成功
// return true;
},
});
weakMap
、Map
和Set
的关系图
完整代码:可以将get和set中的逻辑封装在track和trigger函数中,极大的提高灵活性
const data = { text: "hello world" };
// 用一个全局变量存储被注册的副作用函数
let activeEffect;
// 创建一个新桶来存储副作用函数,包含key和value
const bucket = new WeakMap();
const obj = new Proxy(data, {
get(target, key) { // target:当前对象,key:触发监听的key
track(target, key)
return target[key];
},
set(target, key, newVal) {
target[key] = newVal;
trigger(target, key)
},
});
// effect 函数用于注册副作用函数
function effect(fn) {
activeEffect = fn;
// 执行副作用函数
fn();
}
// 参数是一个匿名的副作用函数
effect(() => {
console.log("执行副作用函数");
document.body.innerHTML = obj.text;
});
setTimeout(() => {
obj.text = 'hello vue3'
// obj.noText = "不存在";
}, 1000);
// track函数
function track(target, key) {
// 没有正在执行的副作用函数 直接返回
if (!activeEffect) return target[key];
// 从这个桶中取出一个Map类型(key -> value)
let depsMap = bucket.get(target);
// 不存在,则创建一个Map与target关联
if (!depsMap) {
bucket.set(target, (depsMap = new Map()));
}
// 根据key判断每个key上是否存在对应的副作用函数
let deps = depsMap.get(key);
// 不存在,则新建一个new Set,并与key关联
if (!deps) {
depsMap.set(key, (deps = new Set()));
}
// 最后将当前激活的副作用函数添加到桶中
deps.add(activeEffect);
}
// trigger函数
function trigger(target, key) {
// 根据target从桶中取得depsMap,它是key --> effects
const depsMap = bucket.get(target)
if(!depsMap) return
// 根据key取得当前对应的副作用函数
const effects = depsMap.get(key)
// 执行副作用函数
effects && effects.forEach(fn => fn())
}
转载自:https://juejin.cn/post/7237996261214994487