我最近经常听到的一个问题是关于 Solid 的反应性。具体来说,是什么让Solid这样的框架比React更有反应性。
答案是Solid的信号所采用的pub/sub模型。使用pub/sub模型,我们能够编写以下代码。
const [count, setCount] = createSignal(0);
setTimeout(() => {
setCount(count() + 1);
}, 1000);
createEffect(() => {
console.log(count());
});
而我们将在每次更新时(每秒)记录计数。你会注意到,我们不需要列举我们的效果的依赖关系--它自动知道,当count 更新时,它应该被触发。
它是如何工作的
为了帮助理解反应性是如何工作的,我们实际上可以建立我们自己的createSignal 和createEffect 基元。这既能让我们对反应性有一些了解,也能 "拉开 "一些被认为是框架的魔法的帷幕。
首先,让我们制作createSignal 。根据我们使用它的方式,我们知道它将接受一个初始值作为参数,并返回一个两元素的元组--一个getter和一个setter。
function createSignal(initialValue) {
let value = initialValue;
function getter() {
return value;
}
function setter(newValue) {
value = newValue;
}
return [getter, setter];
}
重申一下,我们有一个createSignal ,该函数接收一个initialValue ,作为我们的信号。它返回一个双元素数组:第一个元素是getter ,它得到value ,第二个元素是setter ,它将value 设置为newValue 。
接下来,我们需要设置我们的效果。在这个pub/sub模型中,效果的诀窍是:createEffect 将把它调用的函数放在全局范围内,这样信号就可以把它作为一个监听器来捕获。这有很多东西需要摸索,所以让我给你看看这看起来像什么。
let listener;
function createSignal(initialValue) {
let value = initialValue;
const listeners = [];
function getter() {
if (listener) {
listeners.push(listener);
}
return value;
}
function setter(newValue) {
value = newValue;
}
return [getter, setter];
}
function createEffect(func) {
listener = func;
func();
listener = undefined;
}
让我们想一想,当我们调用以下内容时会发生什么。
createEffect(() => {
console.log(count());
});
- 我们有一个全局
listener变量,它被分配到整个函数内部createEffect - 该函数实际被执行
- 当该函数被执行时,
count的getter被调用,它将listener添加到信号的本地listeners数组中。 - 我们将全局监听器设置为
undefined
在我们的createEffect 被调用后,任何在最初执行过程中被调用的信号都会捕捉到作为监听器的效果函数!
最后一步是,每一个信号在其值发生变化时都需要触发其所有的监听器。这可以通过在setter 函数中对listeners 数组的循环来完成。
let listener;
function createSignal(initialValue) {
let value = initialValue;
const listeners = [];
function getter() {
if (listener) {
listeners.push(listener);
}
return value;
}
function setter(newValue) {
value = newValue;
listeners.forEach((listener) => {
listener();
});
}
return [getter, setter];
}
function createEffect(func) {
listener = func;
func();
listener = undefined;
}
这样就形成了一个快速和肮脏的反应式模型我们现在可以看到,我们写的初始代码在count 的值发生变化时(每一秒)都会记录下来。
const [count, setCount] = createSignal(0);
setTimeout(() => {
setCount(count() + 1);
}, 1000);
createEffect(() => {
console.log(count());
});