假如您也和我一样,在准备春招。欢迎加我微信shunwuyu,这里有几十位一心去大厂的友友可以相互鼓励,分享信息,模拟面试,共读源码,齐刷算法,手撕面经。来吧,友友们!
序
在上篇文章中,我们手搓了一个丐帮的reactive,其实在对数据响应式处理的时候还需要考虑一些额外的逻辑,考虑到响应式数据不仅仅是查值改值这么简单外,还有一系列副作用函数的收集和触发,今天我们这篇文章会从依赖收集出发优化一下之前的手写reative代码。
优化版reactive
在之前丐帮的手写reactive我们使用Proxy代理对象的各种操作后只是简单地进行了读值该值,其实不然,我们还需要做一件很重要的事情------依赖收集(也称作副作用函数收集)!
何为依赖收集
我们都知道,一方有难八方支援是我们中国人美好的道德品质,也正是处于我们是同一种族的同胞,有着千丝万缕的关联。在这种关系的连接下,当一个函数触发影响到的响应式对象还会进而牵连许多相关的函数,比如computed和watch等等就是副作用函数,所以我们还需要对这些副作用函数进行收集。我们编程的响应式对象中的任意属性发生修改都应该将用到了这个属性的各个函数重新执行一遍。那么在此之前,需要知道哪些属性被用到了,也就是依赖收集,也称为副作用函数收集。
1. effect
函数创建副作用函数
effect
函数是Vue3中实现响应式的核心之一,它的作用是创建一个响应式的副作用函数。这个副作用函数可以被当作watch
、computed
等功能的基础。让我们看一下effect
函数的主要实现:
js
export function effect(fn, options={}) {
const effectFn = () => {
try {
activeEffect = effectFn
return fn()
} finally {
activeEffect = null
}
}
if (!options.lazy) {
effectFn()
}
effectFn.scheduler = options.scheduler
return effectFn
}
在这段代码中,effect
函数接收一个函数fn
和一些配置项options
。它内部创建了一个effectFn
函数,通过activeEffect
变量来追踪当前的副作用函数。在执行fn
函数时,将activeEffect
设置为当前的effectFn
,从而在track
函数中收集依赖。最后,将effectFn
的scheduler
属性设置为传入的调度函数,并返回这个effectFn
。
2. track
函数收集副作用函数
track
函数用于在属性被读取时收集副作用函数,以建立属性与副作用函数之间的关联关系。下面是track
函数的实现:
js
export function track(target, key) {
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let deps = depsMap.get(key)
if (!deps) {
deps = new Set()
}
if (!deps.has(activeEffect) && activeEffect) {
deps.add(activeEffect)
}
depsMap.set(key, deps)
}
track
函数首先获取目标对象target
在targetMap
中的依赖映射depsMap
,如果不存在,则创建一个新的映射。然后获取属性key
对应的依赖集合deps
,如果不存在,则创建一个新的集合。最后,将当前的副作用函数activeEffect
加入到依赖集合中,并更新depsMap
。
当目标对象被读取值的时候我们就需要做副作用函数的收集
js
function createGetter() {
return function get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
track(target, key); // 收集依赖
return res;
}
}
这样,当属性被读取时,系统就能够追踪到使用该属性的副作用函数,并建立它们之间的关联关系。
3. trigger
函数触发副作用函数
trigger
函数用于触发属性的副作用函数,即在属性被修改时执行相应的副作用函数。下面是trigger
函数的主要实现:
js
export function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) {
return
}
const deps = depsMap.get(key)
if (!deps) {
return
}
deps.forEach(effectFn => {
if (effectFn.scheduler) {
effectFn.scheduler()
} else {
effectFn()
}
});
}
trigger
函数首先获取目标对象target
在targetMap
中的依赖映射depsMap
,如果不存在,则直接返回。然后获取属性key
对应的依赖集合deps
,如果不存在,则直接返回。最后,遍历依赖集合,执行每个副作用函数,如果存在调度函数,则调用调度函数,否则直接执行副作用函数。
当目标对象有属性修改的时候我们就需要做副作用函数的触发,也就是通知所有使用当前属性的副作用函数进行更新
js
function createSetter() {
return function set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver);
trigger(target, key); // 触发依赖
return res;
}
}
这样,当属性被修改时,系统就能够找到所有使用该属性的副作用函数,通知它们进行更新操作,从而保持数据的响应性。
效果
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="module">
import { reactive } from './reactive.js';
import { effect } from './effect.js'
const state = reactive({
name: 'sAnL1ng',
age: 18
})
effect(
() => {
console.log(`${state.name}今年${state.age}岁了`);
},
{
lazy: false
}
)
</script>
</body>
</html>
最后再让我们看一遍效果!
总结
reactive可以将引用类型编程响应式主要依靠于Proxy代理,在对象中的属性被读取的时候需要做副作用函数收集,在对象中的属性被修改的时候需要做副作用函数触发。
假如您也和我一样,在准备春招。欢迎加我微信shunwuyu,这里有几十位一心去大厂的友友可以相互鼓励,分享信息,模拟面试,共读源码,齐刷算法,手撕面经。来吧,友友们!
开源Git仓库: gitee.com/cheng-bingw...
更多内容:最近手有点痒,手搓一个reactive