作者: Marc Backes 翻译:张全玉
这篇文章是从头开始创建Vue.js的系列文章的第四部分,我将在这里教您如何创建诸如Vue.js之类的反应式框架的基础。 要关注此博客文章,建议您首先阅读本系列的其他部分。
路线图🚘
简介
虚拟DOM基础
实现虚拟DOM与渲染
响应式
总结
什么是状态反应式
状态反应式是指当我们的应用程序(变量集)的状态改变时我们做某事(反应)的时间。 我们分两个步骤进行操作:
创建一个“反应性依赖项”(当变量更改时,我们会收到通知) 创建“反应状态”(基本上是依赖变量的集合)
1.建立反应性依赖
监视变化的功能
为此,我们首先需要一个在反应性依赖项更改时执行的函数。 在Vue中,这称为watchEffect; 我们也将其称为函数。
在我们的示例中,此函数如下所示:
function watchEffect(fn) { activeEffect = fn fn() activeEffect = null}全局变量activeEffect是一个临时变量,我们在其中存储传递给watchEffect的函数。 这是必要的,因此我们可以在函数本身读取引用该函数的依赖项时对其进行访问。
依赖类
我们可以将反应性依赖项看作一个变量,当其值更改时通知其订阅者。
可以使用初始值创建它,因此我们需要一个构造函数 我们需要订阅一个函数来更改依赖项。 我们称这个为depend() 当值更改时,我们需要一个通知订阅功能的依赖项。 我们称其为notify() 当值被读取或写入时,我们需要做一些事情,所以我们需要一个getter和setter 所以我们的骨架看起来像这样:
class Dep { // Initialize the value of the reactive dependency constructor(value) {} // Subscribe a new function as observer to the dependency depend() {} // Notify subscribers of a value change notify() {} // Getter of the dependency. Executed when a part of the software reads the value of it. get value() {} // Setter of the dependency. Executed when the value changes set value(newValue) {}}该类具有两个字段:value(依赖项的值)和subscribers(订阅函数集)。
我们将逐步实施此步骤。
构造器
在构造函数中,我们初始化两个字段。
constructor(value) { this._value = value // not `value` because we add getter/setter named value this.subscribers = new Set()}subscribers必须是一个Set,因此我们不会重复订阅相同的功能。
订阅功能 在这里,我们需要为观察者订阅一个新函数。 我们称之为依赖。
depend() { if (activeEffect) this.subscribers.add(activeEffect)}activeEffect是在watchEffect中设置的临时变量,本教程后面将对此进行说明。
通知订阅者依赖关系更改
当值更改时,我们将调用此函数,因此我们可以在相关性值更改时通知所有订阅者。
notify() { this.subscribers.forEach((subscriber) => subscriber())}我们在这里要做的是执行每个subscriber。 切记:这是一个subscriber是一项功能。
Geter
在依赖项的获取器中,我们需要将activeEffect(当依赖项发生更改时将执行的函数)添加到订阅服务器列表。 换句话说,使用我们前面定义的depend()方法。
结果,我们返回当前值。
get value() { this.depend() return this._value}Setter
在依赖项的setter中,我们需要执行所有监视此依赖项的功能(订阅者)。 换句话说,使用我们前面定义的notify()方法。
set value(newValue) { this._value = newValue this.notify()}试试看
依赖关系的实现已完成。 现在是时候尝试一下了。 为此,我们需要做三件事:
定义一个依赖
添加要在依赖项更改时执行的功能
更改依赖项的值
// Create a reactive dependency with the value of 1const count = new Dep(1)// Add a "watcher". This logs every change of the dependency to the console.watchEffect(() => { console.log('👻 value changed', count.value)})// Change valuesetTimeout(() => { count.value++}, 1000)setTimeout(() => { count.value++}, 2000)setTimeout(() => { count.value++}, 3000)在控制台日志中,您应该能够看到以下内容:
👻 value changed 1👻 value changed 2👻 value changed 3👻 value changed 42.建立反应状态
这只是难题的第一部分,主要是更好地了解接下来将要发生的事情。
回顾一下:我们有一个反应性依赖项和一个watch函数,它们使我们能够在变量(依赖项)发生变化时执行一个函数。 这已经很酷了。 但是我们想更进一步,创造一个状态。
而不是像这样的东西:
const count = Dep(1)const name = Dep('Marc')id.value = 2name.value = 'Johnny'我们想做这样的事情:
const state = reactive({ count: 1, name: 'Marc',})state.count = 2state.name = 'Johnny'为此,我们需要对代码进行一些更改:
添加反应函数。 这创建了“状态”对象。
将getter和setter移至状态而不是依赖项(因为这是发生更改的地方)
因此,依存关系(Dep)仅会如此。 仅依赖项部分,不包含任何值。 值存储在状态中。
反应函数
reactive()函数可以看作是状态的初始化。我们将一个带有初始值的对象传递给它,然后将其转换为依赖项。
对于每个对象属性,必须执行以下操作:
定义依赖项(
Dep)定义getter
定义setter
function reactive(obj) { Object.keys(obj).forEach((key) => { const dep = new Dep() let value = obj[key] Object.defineProperty(obj, key, { get() { dep.depend() return value }, set(newValue) { if (newValue !== value) { value = newValue dep.notify() } }, }) }) return obj}依赖关系的变化
另外,我们需要从依赖项中删除getter和setter,因为我们现在是在反应状态下进行的:
class Dep { subscribers = new Set() depend() { if (activeEffect) this.subscribers.add(activeEffect) } notify() { this.subscribers.forEach((sub) => sub()) }}watchEffect函数保持不变。
试用代码
而且我们已经完成了将依赖变量转换为反应状态的工作。 现在我们可以尝试代码:
const state = reactive({ count: 1, name: 'Marc',})watchEffect(() => { console.log('👻 state changed', state.count, state.name)})setTimeout(() => { state.count++ state.name = 'Johnny'}, 1000)setTimeout(() => { state.count++}, 2000)setTimeout(() => { state.count++}, 3000)在控制台日志中,您应该看到如下所示:
👻 state changed 1 Marc👻 state changed 2 Marc👻 state changed 2 Johnny👻 state changed 3 Johnny👻 state changed 4 Johnny总结✨
这就是本系列的这一部分。 我们做了以下工作:
创建一个具有内部值的依赖项,该依赖项在值更改时通知订阅的函数。
创建一个状态,在该状态中,将为每个值的更改调用预订的函数。