Advanced Concepts

细粒度响应式

编辑此页面

响应式确保自动响应数据更改,而无需手动更新用户界面 (UI)。通过将 UI 元素连接到底层数据,更新变得自动化。在细粒度响应式系统中,应用程序能够进行高度有针对性的和具体的更新。

Solid 和 React 之间的对比就是一个例子。在 Solid 中,只对需要更改的目标属性进行更新,从而避免更广泛且有时不必要的更新。相比之下,React 会针对单个属性的更改重新执行整个组件,这可能会降低效率。

由于细粒度响应式系统,不必要的重新计算将被避免。通过仅针对发生更改的应用程序区域,用户体验将变得更加流畅。

注意:如果您对响应式概念不熟悉并且想要学习基础知识,请考虑从我们的响应式介绍指南开始。


响应式原语

在 Solid 的响应式系统中,有两个关键元素:信号(signals)和观察者(observers)。这些核心元素是更专业的响应式功能的基础:

  • Stores 本质是代理,用于在底层创建、读取和写入信号。
  • Memos 类似于 effects,但不同之处在于它返回 signals 并通过缓存优化计算。它们基于 effect 行为进行更新,但更适合计算优化。
  • Resources 建立在 memos 概念之上,将网络请求的异步性转换为同步性,将结果嵌入到 signal 中。
  • Render effects 是一种立即启动的定制 effect 类型,专为管理渲染过程而设计。

了解信号

Signals 就像可变变量,可以指向现在的值和将来的另一个值。它们由两个主要功能组成:

  • Getter:如何读取信号的当前值。
  • Setter:一种修改或更新信号值的方法。

在 Solid 中,createSignal 函数可用于创建信号。此函数将 getter 和 setter 作为数组中的元素返回:

import { createSignal } from "solid-js";
const [count, setCount] = createSignal(1);
console.log(count()); // prints "1"
setCount(0); // changes count to 0
console.log(count()); // prints "0"

在这个例子中,count 是 getter,setCount就是 setter。

Effects

Effects 是当它们所依赖的 signals 指向不同值时触发的函数。它们可以被认为是自动响应器,signals 值的任何变化都会触发 effect 运行。

import { createSignal, createEffect } from "solid-js";
const [count, setCount] = createSignal(0);
createEffect(() => {
console.log(count());
});

在这个例子中,effect 依赖 count,只要 count 发生改变,就会调用该函数。


构建响应式系统

要掌握响应式的概念,从头开始构建响应式系统通常会有所帮助。

以下示例遵循观察者模式,其中数据实体(signals)将维护其订阅者(effects)列表。这是一种在订阅者观察到的 signal 发生变化时通知订阅者的方法。

以下是代码大纲:

function createSignal() {}
function createEffect() {}
const [count, setCount] = createSignal(0);
createEffect(() => {
console.log("The count is " + count());
});

响应式原语

createSignal

createSignal 函数执行两个主要任务:

  1. 初始化该值(在本例中, count 设置为 0 )。
  2. 返回一个包含两个元素的数组:getter 和 setter 函数。
function createSignal(initialValue) {
let value = initialValue;
function getter() {
return value;
}
function setter(newValue) {
value = newValue;
}
return [getter, setter];
}
// ..

这允许您通过 getter 检索当前值并通过 setter 进行任何更改。然而,在此阶段,还不存在响应式。

createEffect

createEffect 定义一个函数,该函数立即调用传递给它的函数:

// ..
function createEffect(fn) {
fn();
}
// ..

使系统具有响应式

当链接 createSignalcreateEffect 时会出现响应式,这是通过以下方式发生的:

  1. 维护对当前订阅者函数的引用
  2. 在创建 effect 期间注册此函数。
  3. 访问 signal 时将该函数添加到订阅者列表中。
  4. signal 更新时通知所有订阅者。
let currentSubscriber = null;
function createSignal(initialValue) {
let value = initialValue;
const subscribers = new Set();
function getter() {
if (currentSubscriber) {
subscribers.add(currentSubscriber);
}
return value;
}
function setter(newValue) {
if (value === newValue) return; // if the new value is not different, do not notify dependent effects and memos
value = newValue;
for (const subscriber of subscribers) {
subscriber(); //
}
}
return [getter, setter];
}
// creating an effect
function createEffect(fn) {
const previousSubscriber = currentSubscriber; // Step 1
currentSubscriber = fn;
fn();
currentSubscriber = previousSubscriber;
}
//..

一个变量(currentSubscriber)用于保存对当前正在执行的订阅者函数的引用。这用于确定哪些 effects 取决于哪些 signals。

createSignal 内部,初始值被存储,并且使用 Set 来存储依赖于 signal 的任何订阅者函数。然后,该函数将返回该 signal 的两个函数:

  • getter 函数检查当前订阅者函数是否正在被访问,如果是,则在返回信号的当前值之前将其添加到订阅者列表中。
  • setter 函数根据旧值评估新值,仅在 signal 更新时通知相关函数。

创建 createEffect 函数时,将初始化对任何先前订阅者的引用,以处理可能的嵌套效果。然后,当前订阅者被传递给给定的函数,该函数会立即运行。在此运行期间,如果 effect 访问任何 signals,则会将其注册为这些 signals 的订阅者。一旦给定的函数运行完毕,当前订阅者将重置为其之前的值,以便在存在任何嵌套 effect 时,它们可以正确运行。

验证响应式系统

要验证系统,请以一秒的间隔递增 count 值:

//..
const [count, setCount] = createSignal(0);
createEffect(() => {
console.log("The count is " + count());
});
setInterval(() => {
setCount(count() + 1);
}, 1000);

这将以一秒的间隔在控制台上打印递增的计数值,以确认响应式系统的功能。


管理响应式系统中的生命周期

在响应式系统中,各种元素(通常称为“节点”)是相互连接的。这些节点可以是 signals、effects 或其他响应式原语。它们作为独立的单元共同构成系统的响应式行为。

当一个节点发生变化时,系统将重新评估连接到该节点的部件。这可能会导致连接的更新、添加或删除,从而影响系统的整体行为。

现在,考虑一个根据条件影响计算输出数据的场景:

Temperature.jsx
console.log("1. Initialize");
const [temperature, setTemperature] = createSignal(72);
const [unit, setUnit] = createSignal("Fahrenheit");
const [displayTemp, setDisplayTemp] = createSignal(true);
const displayTemperature = createMemo(() => {
if (!displayTemp()) return "Temperature display is off";
return `${temperature()} degrees ${unit()}`;
});
createEffect(() => console.log("Current temperature is", displayTemperature()));
console.log("2. Turn off displayTemp");
setDisplayTemp(false);
console.log("3. Change unit");
setUnit("Celsius");
console.log("4. Turn on displayTemp");
setDisplayTemp(true);

在这个例子中,createMemo 用于缓存计算状态。这意味着如果其依赖项保持不变,则不必重新运行计算。

displayTemperature memo 具有基于 displayTemp 值的提前返回条件。当 displayTempfalse 时,memo 会返回一条消息,指出“Temperature display is off”,因此不会跟踪 temperatureunit

但是,如果 unitdisplayTempfalse 时发生更改,则不会触发该 effect,因为 memo 的当前依赖项(本例中为 displayTemp)没有改变。

effect 跟踪是同步的

上述响应式系统同步运行。这个特性对于如何跟踪 effect 及其依项项有影响。具体来说,系统注册订阅者,运行 effect 函数,然后注销订阅者——所有这些都是以线性、同步的顺序进行的。

考虑以下示例:

createEffect(() => {
setTimeout(() => {
console.log(count());
}, 1000);
});

这个例子中的 createEffect 函数启动 setTimeout 来延迟控制台日志。因为系统是同步的,所以它不会等待此操作完成。当 count getter 在 setTimeout 内触发时,全局范围不再有注册订阅者。因此,这个 count 信号不会将回调添加为订阅者,这会导致跟踪 count 更改时可能会出现问题。

处理异步 effect

虽然基础响应式系统是同步的,但像 Solid 这样的框架提供了更高级的功能来处理异步场景。例如,on 函数提供了一种手动指定 effect 依赖项的方法。这对于确保异步操作正确地绑定到响应式系统中特别有用。

Solid 还引入了用于管理异步操作的 resources 概念。Resources 是专门的响应式原语,它将网络请求等操作的异步性转换为同步性,并将结果嵌入 signal 中。然后,系统可以跟踪异步操作及其状态,从而在操作完成或其状态更改时保持 UI 最新。

当涉及多个异步操作并且完成可能会影响响应式系统的不同部分时,使用 Solid 中的 Resources 可以在复杂的场景中提供帮助。通过将 resources 集成到系统中,您可以确保正确跟踪依赖项,并且 UI 与底层异步数据保持一致。

报告此页面问题