文章目录
- 作为Solid JS响应式的基石,我们先看看createSignal的用法和原理。接着我们手动实现一个简易版的createSignal,
-
- function createSignal<T>( initialValue: T, options?: { equals?: false | ((prev: T, next: T) => boolean) } ): [get: () => T, set: (v: T) => T]; Solid JS的厉害之处是,你可以定义变量是否为响应式,甚至可以定义响应式的时机。 仅提供initialValue时,(默认)是响应式的。 在options设置equals为false时不管何时都是响应式。 equals设置为函数,根据新值和旧值的关系来设置何时为响应式。
- 下面这个例子仅仅在新的值大于旧的值(新增)时,才是响应式的。 import { render } from "solid-js/web"; import { createSignal } from "solid-js"; function Counter() { const [count, setCount] = createSignal(1, { equals: (n, o) => n > o }); const increment = () => setCount(count() + 1); const reduce = () => setCount(count() - 1); return ( <> <button type="button" onClick={increment}> + </button> <button type="button">{count()}</button> <button type="button" onClick={reduce}> - </button> </> ); } render(() => <Counter />, document.getElementById("app")!);
- createSignal简化后的逻辑如下:
- const signalOptions = { equals: false }; function createSignal(value, options) { // 初始化options options = options ? Object.assign({}, signalOptions, options) : signalOptions; // 创建内部signal const s = { value, comparator: options.equals || undefined }; // 定义setter const setter = value => { if (typeof value === "function") { value = value(s.value); } return writeSignal(s, value); }; // 返回[getter, setter] return [readSignal.bind(s), setter]; } // 返回当前内部signal的value function readSignal() { return this.value; } // 更新内部的value,然后返回value function writeSignal(node, value) { if (!node.comparator) { node.value = value; } return value; } 现在我们已经实现了createSignal基本功能了,接下来我们通过实现createEffect来让它具有响应式的能力。
-
- createEffect接受一个副作用函数,每当它依赖的状态发生改变时,这个副作用都被执行一次。 function createEffect<T>(fn: (v: T) => T, value?: T): void;
- 这是个很常见的例子。 import { render } from "solid-js/web"; import { createSignal, createEffect } from "solid-js"; function Counter() { const [count, setCount] = createSignal(1); const increment = () => setCount(count() + 1); createEffect(() => console.log('count : ', count())) return ( <button type="button" onClick={increment}> {count()} </button> ); } render(() => <Counter />, document.getElementById("app")!);
- 我们已经知道,当createEffect依赖项发生改变时,副作用会也会发生改变,这是因为createSignal是基于发布订阅模式的响应式。一个较为完整的关系如下:
- const signalOptions = { equals: false }; const observers = [] function createEffect (effect) { const execute = () => { // 保存在observers中 observers.push(execute); try { effect(); } finally { // 释放 observers.pop(); } }; // 副作用函数立即执行 execute(); }; function createSignal(value, options) { // 初始化options options = options ? Object.assign({}, signalOptions, options) : signalOptions; // 创建内部signal const s = { value, // 保存订阅者 subscribers: new Set(), comparator: options.equals || undefined }; // 定义setter const setter = value => { if (typeof value === "function") { value = value(s.value); } return writeSignal(s, value); }; // 返回[getter, setter] return [readSignal.bind(s), setter]; } // 返回当前内部signal的value function readSignal() { const curr = observers[observers.length - 1] curr && this.subscribers.add(curr) return this.value; } // 更新内部的value,然后返回value function writeSignal(node, value) { if (!node.comparator) { node.value = value; } // 每次写入时执行对应的订阅者 node.subscribers.forEach((subscriber) => subscriber()); return value; } 现在我们准备下面的html文件 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>SolidJS</title> </head> <body> <h1>打开控制台查看结果</h1> <script src="./solid.js"></script> <script> const [count, setCount] = createSignal(1); const increment = () => setCount(count() + 1); createEffect(() => console.log('count : ', count())) window.increment = increment </script> </body> </html> 使用window.increment模拟点击事件,打印如下。 下面我们实现createMemo
-
- createMemo通常用来做派生变量保存基于某个状态中间值。完整用法如下: function createMemo<T>( fn: (v: T) => T, value?: T, options?: { equals?: false | ((prev: T, next: T) => boolean) } ): () => T; 本篇只讨论最原始的memo。
- 一个例子如下,每当count变化时,sum自动加2 import { render } from "solid-js/web"; import { createSignal, createEffect, createMemo } from "solid-js"; function Counter() { const [count, setCount] = createSignal(1); const increment = () => setCount(count() + 1); const sum = createMemo(() => count() + 2) createEffect(() => console.log('sum : ', sum())) createEffect(() => console.log('count : ', count())) return ( <button type="button" onClick={increment}> {count()} </button> ); } render(() => <Counter />, document.getElementById("app")!);
- 它的内部是使用createSignal实现的,所以流程上来说和createEffect一样。 真实的源码里,是基于createComputation实现的,但是它的内部是createSignal
- const createMemo = (memo) => { const [value, setValue] = createSignal(); createEffect(() => setValue(memo())); return value; }; 接下来在测试例子里添加如下两行 const sum = createMemo(() => count() + 2) createEffect(() => console.log('sum : ', sum())) 然后在控制台操作
-
上篇文章中主要介绍了Solid JS的基本语法,分阶段粗略地介绍了一些原理(响应式原理、编译原理和运行时原理)。
接下来的几篇文章里我会详细介绍每个阶段的详细实现原理,希望可以给你的学习带来帮助。
写这篇文章的时候有很大的犹豫,担心Solid JS受众太小,文章的反响连”平平“都算不上,所以先写一篇试试水,如果真的反响平平,我会暂时放弃这个写作计划,还请见谅!
作为Solid JS响应式的基石,我们先看看createSignal的用法和原理。接着我们手动实现一个简易版的createSignal,
function createSignal<T>(
initialValue: T,
options?: { equals?: false | ((prev: T, next: T) => boolean) }
): [get: () => T, set: (v: T) => T];
function createSignal<T>(
initialValue: T,
options?: { equals?: false | ((prev: T, next: T) => boolean) }
): [get: () => T, set: (v: T) => T];Solid JS的厉害之处是,你可以定义变量是否为响应式,甚至可以定义响应式的时机。
- 仅提供initialValue时,(默认)是响应式的。
- 在options设置equals为false时不管何时都是响应式。
- equals设置为函数,根据新值和旧值的关系来设置何时为响应式。
下面这个例子仅仅在新的值大于旧的值(新增)时,才是响应式的。
import { render } from "solid-js/web";
import { createSignal } from "solid-js";
function Counter() {
const [count, setCount] = createSignal(1, { equals: (n, o) => n > o });
const increment = () => setCount(count() + 1);
const reduce = () => setCount(count() - 1);
return (
<>
<button type="button" onClick={increment}>
+
</button>
<button type="button">{count()}</button>
<button type="button" onClick={reduce}>
-
</button>
</>
);
}
render(() => <Counter />, document.getElementById("app")!);
createSignal简化后的逻辑如下:

const signalOptions = {
equals: false
};
function createSignal(value, options) {
// 初始化options
options = options
? Object.assign({}, signalOptions, options)
: signalOptions;
// 创建内部signal
const s = {
value,
comparator: options.equals || undefined
};
// 定义setter
const setter = value => {
if (typeof value === "function") {
value = value(s.value);
}
return writeSignal(s, value);
};
// 返回[getter, setter]
return [readSignal.bind(s), setter];
}
// 返回当前内部signal的value
function readSignal() {
return this.value;
}
// 更新内部的value,然后返回value
function writeSignal(node, value) {
if (!node.comparator) {
node.value = value;
}
return value;
}
const signalOptions = {
equals: false
};
function createSignal(value, options) {
// 初始化options
options = options
? Object.assign({}, signalOptions, options)
: signalOptions;
// 创建内部signal
const s = {
value,
comparator: options.equals || undefined
};
// 定义setter
const setter = value => {
if (typeof value === "function") {
value = value(s.value);
}
return writeSignal(s, value);
};
// 返回[getter, setter]
return [readSignal.bind(s), setter];
}
// 返回当前内部signal的value
function readSignal() {
return this.value;
}
// 更新内部的value,然后返回value
function writeSignal(node, value) {
if (!node.comparator) {
node.value = value;
}
return value;
}现在我们已经实现了createSignal基本功能了,接下来我们通过实现createEffect来让它具有响应式的能力。
createEffect接受一个副作用函数,每当它依赖的状态发生改变时,这个副作用都被执行一次。
function createEffect<T>(fn: (v: T) => T, value?: T): void;
这是个很常见的例子。
import { render } from "solid-js/web";
import { createSignal, createEffect } from "solid-js";
function Counter() {
const [count, setCount] = createSignal(1);
const increment = () => setCount(count() + 1);
createEffect(() => console.log('count : ', count()))
return (
<button type="button" onClick={increment}>
{count()}
</button>
);
}
render(() => <Counter />, document.getElementById("app")!);
我们已经知道,当createEffect依赖项发生改变时,副作用会也会发生改变,这是因为createSignal是基于发布订阅模式的响应式。一个较为完整的关系如下:

const signalOptions = {
equals: false
};
const observers = []
function createEffect (effect) {
const execute = () => {
// 保存在observers中
observers.push(execute);
try {
effect();
} finally {
// 释放
observers.pop();
}
};
// 副作用函数立即执行
execute();
};
function createSignal(value, options) {
// 初始化options
options = options
? Object.assign({}, signalOptions, options)
: signalOptions;
// 创建内部signal
const s = {
value,
// 保存订阅者
subscribers: new Set(),
comparator: options.equals || undefined
};
// 定义setter
const setter = value => {
if (typeof value === "function") {
value = value(s.value);
}
return writeSignal(s, value);
};
// 返回[getter, setter]
return [readSignal.bind(s), setter];
}
// 返回当前内部signal的value
function readSignal() {
const curr = observers[observers.length - 1]
curr && this.subscribers.add(curr)
return this.value;
}
// 更新内部的value,然后返回value
function writeSignal(node, value) {
if (!node.comparator) {
node.value = value;
}
// 每次写入时执行对应的订阅者
node.subscribers.forEach((subscriber) => subscriber());
return value;
}
const signalOptions = {
equals: false
};
const observers = []
function createEffect (effect) {
const execute = () => {
// 保存在observers中
observers.push(execute);
try {
effect();
} finally {
// 释放
observers.pop();
}
};
// 副作用函数立即执行
execute();
};
function createSignal(value, options) {
// 初始化options
options = options
? Object.assign({}, signalOptions, options)
: signalOptions;
// 创建内部signal
const s = {
value,
// 保存订阅者
subscribers: new Set(),
comparator: options.equals || undefined
};
// 定义setter
const setter = value => {
if (typeof value === "function") {
value = value(s.value);
}
return writeSignal(s, value);
};
// 返回[getter, setter]
return [readSignal.bind(s), setter];
}
// 返回当前内部signal的value
function readSignal() {
const curr = observers[observers.length - 1]
curr && this.subscribers.add(curr)
return this.value;
}
// 更新内部的value,然后返回value
function writeSignal(node, value) {
if (!node.comparator) {
node.value = value;
}
// 每次写入时执行对应的订阅者
node.subscribers.forEach((subscriber) => subscriber());
return value;
}现在我们准备下面的html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SolidJS</title>
</head>
<body>
<h1>打开控制台查看结果</h1>
<script src="./solid.js"></script>
<script>
const [count, setCount] = createSignal(1);
const increment = () => setCount(count() + 1);
createEffect(() => console.log('count : ', count()))
window.increment = increment
</script>
</body>
</html>
使用window.increment模拟点击事件,打印如下。

下面我们实现createMemo
createMemo通常用来做派生变量保存基于某个状态中间值。完整用法如下:
function createMemo<T>(
fn: (v: T) => T,
value?: T,
options?: { equals?: false | ((prev: T, next: T) => boolean) }
): () => T;
本篇只讨论最原始的memo。
一个例子如下,每当count变化时,sum自动加2
import { render } from "solid-js/web";
import { createSignal, createEffect, createMemo } from "solid-js";
function Counter() {
const [count, setCount] = createSignal(1);
const increment = () => setCount(count() + 1);
const sum = createMemo(() => count() + 2)
createEffect(() => console.log('sum : ', sum()))
createEffect(() => console.log('count : ', count()))
return (
<button type="button" onClick={increment}>
{count()}
</button>
);
}
render(() => <Counter />, document.getElementById("app")!);
它的内部是使用createSignal实现的,所以流程上来说和createEffect一样。
真实的源码里,是基于createComputation实现的,但是它的内部是createSignal

const createMemo = (memo) => {
const [value, setValue] = createSignal();
createEffect(() => setValue(memo()));
return value;
};
const createMemo = (memo) => {
const [value, setValue] = createSignal();
createEffect(() => setValue(memo()));
return value;
};接下来在测试例子里添加如下两行
const sum = createMemo(() => count() + 2)
createEffect(() => console.log('sum : ', sum()))
然后在控制台操作
