Reactivity_Dep

本篇文章主要是解析尤雨溪在codepen上实现的一段Reactivity(Dep)代码,我们拆成2部分来看。

Part 1 实现响应式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let activeEffect

class Dep {
subscribers = new Set()
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect)
}
}
notify() {
this.subscribers.forEach(effect => effect())
}
}

function watchEffect(effect) {
activeEffect = effect
effect()
}

首先,定义了一个全局变量activeEffect。

然后写了一个Dep类,它有一个subscribers属性,depend和notify方法。
subscribers是Set对象,Set对象允许存储任何类型的唯一值,无论是原始值或者是对象引用。

Set

调用depend时,如果activeEffect存在,就将其加到subscribers中。
调用notify时,遍历subscribers,执行遍历到的对象。

watchEffect函数接收一个effect参数,调用时将effect参数赋值给全局变量activeEffect,然后执行effect。

从上面的解析可以看到,代码实现的非常简陋,没有做类型判断,所以调用时注意传参的类型,watchEffect传参须是函数。

现在功能实现好了,怎么用呢?我们继续往下看。

Part 2 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// usage -----------------------

const dep = new Dep()

let actualCount = 0
const state = {
get count() {
dep.depend()
return actualCount
},
set count(newCount) {
actualCount = newCount
dep.notify()
}
}

watchEffect(() => {
console.log(state.count)
}) // 0

state.count++ // 1

我们在适当的地方加上打印,那么程序的走向将更加明了:

watchEffect

上面的打印能看懂,就不用看下面这些了。

首先创建一个Dep对象类型的实例dep。

然后创建一个actualCount变量,并初始化,值为0。

然后创建一个state变量,state变量内部使用get和set语法。
其中get语法和set语法分别将对象属性和一个函数绑定,在获取或设置该属性时,触发绑定的函数执行。
上面创建并初始化state的代码意思是获取state.count时,执行get后面的函数,调用dep.depend,然后返回actualCount。
设置state.count时,比如state.count = 1,就执行set后面的函数,把1赋值给actualCount,然后调用dep.notify。

然后调用watchEffect函数,传入的参数是匿名函数() => { console.log(state.count) }。
watchEffect函数中,将传入的匿名函数赋值给全局变量activeEffect,然后执行这个匿名函数,于是打印state.count的值,而打印state.count的值,就需要获取state.count,就会触发get绑定的函数,于是执行dep.depend,由于此时的activeEffect值是匿名函数,所以将该匿名函数加到subscribers中,然后返回actualCount,所以得到的值是0,打印了0。

最后state.count++,使state.count自增1,这里先获取再自增,所以会触发get和set,由于全局变量activeEffect一直存在,所以get时会将activeEffect加入subscribers,而subscribers内的值不会重复,所以一直就只有一个值,那就是最开始加入的匿名函数。
触发set时就执行和set绑定的函数,把1赋值给actualCount,然后执行dep.notify,notify中遍历subscribers,遍历到了之前加入的匿名函数,就执行了该匿名函数,于是打印了1。

总结

首先我们翻译几个名词。

  • subscribers: 订阅者,用户
  • depend: 依赖
  • notify: 通知,公布
  • actual: 目前的
  • effect: 达到目的

从这些名词中,你是不是已经想到了一些javascript的设计模式?这里就不做讨论,感兴趣的话自行学习。

所以,上面的代码,翻译成白话,调用watchEffect来执行一个函数,这个函数获取了一个值count,于是把这个函数加到监听者列表,当count变化时,就会遍历监听者列表,于是遍历到刚刚加入的函数,于是执行了它,于是就获取到了当前的count值(actualCount)。

打个比方,同学小明,查询(watchEffect)了一下iphone12(state)的价格(count),获取价格时系统就把小明加入订阅者,在价格变化时,通知小明同学最新的价格。

这一期的代码比较简陋,但是循序渐进,由简入深,下期我们继续讲解进阶版。