赞
踩
在开始设计一个响应式系统之前,我们应该了解什么是响应式数据,以及什么是副作用函数。
function effect () {
document.body.innerText = "Hello Vue3"
}
解释:当effect函数执行的时候,会改变body的innerText,但是body的innerText可能在其他任何地方都会被使用。这样effect函数的执行影响了其他函数。我们认为effect函数产生了副作用。
我们希望的是,所改变的数据都是响应式的,所谓响应式数据,就是在数据发生变化时候,它的副作用函数会重新执行。
例如:
const obj = {text: "Hello Vue3"}
function effect () {
document.body.innerText = obj.text;
}
解释:如上代码所写,obj.text在副作用函数effect中引用,我们希望的是,当obj.text一旦发生变化,我们就执行副作用effect函数,这样会永远保证document.body.innerText的值是我们最新改变的那个值。
在了解了什么是响应式数据以及副作用函数之后,现在的问题变成了我们如何才能让上文的obj变成响应式数据呢?我们有两点线索可以发现。
// 原始数据 const data = { text: "Hello Vue" } // 设置一个用来存放副作用函数的桶 const bucket = new Set(); // 将原始数据用proxy来进行代理 const obj = new Proxy(data, { // 拦截obj的读取操作 get(target, key) { // 读取obj的值的时候,将副作用函数存放进bucket桶中 bucket.add(effect); // 返回属性值 return target[key]; }, // 拦截obj的设置操作 set(target, key, newVal) { // 将原始值设置为最新的值 target[key] = newVal; // 设置完之后将bucket桶中拿出effect()执行 bucket.forEach(fn => fn()); // true代表设置成功 return true; } }) // 实验 // effect副作用函数 const effect = () => { document.body.innerText = obj.text; } // 执行副作用函数effect,触发obj.text的读取操作(get) effect(); // 设置一个定时器,在三秒之后修改obj.text的值,触发它的设置操作(set) setTimeout(() => { obj.text = "Hello Vue3" }, 3000) // 浏览器会在三秒之后从原来的Hello Vue改为Hello Vue3
在实现了一个最基本的响应式数据之后,其实上面的代码还存在着很多问题,接下来我们一一解决这些问题,同时完善我们的响应式系统。
// 原始数据 const data = { text: "Hello Vue" } // 设置一个用来存放副作用函数的桶 const bucket = new Set(); // 将原始数据用proxy来进行代理 const obj = new Proxy(data, { // 拦截obj的读取操作 get(target, key) { // 读取obj的值的时候,将副作用函数存放进bucket桶中 // bucket.add(effect); 废弃 // 如果activityEffect存在 if (activityEffect) { // 读取obj的值的时候,将副作用函数存放进bucket桶中 bucket.add(activityEffect); } // 返回属性值 return target[key]; }, // 拦截obj的设置操作 set(target, key, newVal) { // 将原始值设置为最新的值 target[key] = newVal; // 设置完之后将bucket桶中拿出effect()执行 bucket.forEach(fn => fn()); // true代表设置成功 return true; } }) // 定义一个全局变量,用来接收副作用函数 let activityEffect; // effect用来注册副作用函数,接收一个副作用函数fn作为参数 const effect = (fn) => { // 保存副作用函数 activityEffect = fn; // 执行副作用函数 fn() } // newEffect新的副作用函数,名字不再局限于effect const newEffect = () => { // 触发obj的读取操作 document.body.innerText = obj.text; } // 执行注册副作用函数effect effect(newEffect); // 设置一个定时器,在三秒之后修改obj.text的值,触发它的设置操作(set) setTimeout(() => { obj.text = "Hello Vue3" }, 3000) // 浏览器会在三秒之后从原来的Hello Vue改为Hello Vue3
effect( () => {
console.log("我被打印了!")
document.body.innerText = obj.text;
});
setTimeout(() => {
obj.newText = "Hello Vue3"
}, 3000)
多出来的一次打印是因为在obj上设置了newText导致触发了obj的set操作,而set操作回把桶中的副作用函数拿出来执行一次。
// 原始数据 const data = { text: "Hello Vue" } // 设置一个用来存放副作用函数的桶--WeakMap const bucket = new WeakMap(); // 将原始数据用proxy来进行代理 const obj = new Proxy(data, { // 拦截obj的读取操作 get(target, key) { // 如果activityEffect不存在,我们不需要收集副作用函数 if (!activityEffect) return; // 如果存在 我们需要从桶中取出target对应的Map,这个Map中存放的是key let depsMap = bucket.get(target); // 如果depsMap不存在,说明是第一次读取,我们需要将其添加为响应式数据,并且将副作用函数存在桶中 if (!depsMap) { bucket.set(target, (depsMap = new Map())); } // 我们需要从Map中取出key对应的Set,这个Set中存放的是effectFn let deps = depsMap.get(key); // 如果deps不存在,说明该属性还没有副作用函数,将该effectFn添加到Set当中 if (!deps) { depsMap.set(key, (deps = new Set())); } deps.add(activityEffect); // 返回属性值 return target[key]; }, // 拦截obj的设置操作 set(target, key, newVal) { // 将原始值设置为最新的值 target[key] = newVal; // 将WeakMap中取出target对应的key const depsMap = bucket.get(target); // 如果不存在,说明没有副作用函数,直接返回就行 if (!depsMap) return true; // 如果存在,将读取的key对应的存放effecfFn从Set中取出来 const effectFns = depsMap.get(key); // 如果Set中存在副作用函数,就执行副作用函数 effectFns && effectFns.forEach(fn => fn()); } }) // 定义一个全局变量,用来接收副作用函数 let activityEffect; // effect用来注册副作用函数,接收一个副作用函数fn作为参数 const effect = (fn) => { // 保存副作用函数 activityEffect = fn; // 执行副作用函数 fn() } // newEffect新的副作用函数,名字不再局限于effect const newEffect = () => { console.log("我被打印了") // 触发obj的读取操作 document.body.innerText = obj.text; } // 执行注册副作用函数effect effect(newEffect); // 设置一个定时器,在三秒之后设置一个新属性 setTimeout(() => { obj.newtext = "Hello Vue3" }, 3000) // "我被打印了" 只打印了一次
这样我们就简单的实现了一个微响应式系统,其实上面的代码get和set中可以做一些灵活性的封装,有兴趣的可以试试。
有了这篇的基础,下一周可以写一下Vue3的非基本类型的响应式原理!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。