简介
Solidjs 是一个类似于 React & Vue 的前端框架。它实现了细粒度的自动依赖追踪,当状态变化时,只会触发直接依赖它的视图的更新。它没有虚拟 DOM,而是在编译阶段将 JSX 模版转换为原生 DOM 操作指令,运行时直接操作真实 DOM 节点。
核心思想:核心设计理念是"声明式编程",类似于React,但更接近于Vue的模板语法。借助Signal实现状态到UI的极精细化同步,这种同步的最小单位是DOM元素,框架能够通过最小化的修改去满足程序员的要求。
实现原理:
响应式原理:参考S.js,核心是将state和dom或者Component做关联。当调用createSignal时产生一个响应式数据signalstate等待收集依赖;当insert时将dom操作封装成一个函数存放到signalstate的observers中;当setCount触发signalstate中的observers执行视图更新。
编译:提供了支持多种源到产物的编译方式,如将jsx编译为产物、将tag template转成产物、将HyperScript转成产物。
运行时:提供运行时api支持,例如render、insert、createComponent、template等功能。
优势:
高性能:接近原生的性能,在js - framework - benchmark排名中名列前茅,通过直接操作DOM而非虚拟DOM实现高性能,减少额外渲染层开销,且状态改变时只更新受影响部分,而非整个组件树。
极小的打包体积:编译为直接的DOM操作,无虚拟DOM,极小的运行时,适合打为独立的webComponent在其它应用中嵌入。
易于使用:近似React的使用体验,便于快速上手,API设计直观且易于理解,从其他React或Vue背景的开发者能够轻松上手。
强大的响应式系统:不仅限于状态,还可用于跟踪计算属性,使代码更整洁。
劣势:配套的生态不成熟。
原理
响应式 & Signal
createSignal
signal
是solid
中基本的响应单元,createSignal
类似react
中的useState
,传递给createSignal
调用的参数是初始值,createSignal
返回[getter, setter]
,需要注意:第一个返回的值是一个getter
而不是一个值,使用的时候需要调用。框架拦截读取值的任何位置来进行自动跟踪,从而响应式更新,所以调用getter的位置很重要。
和
react
不同的是,例如setState
触发更新,react
会生成Fiber
树,进行diff
算法,最后执行dom
操作。solid
则是直接调用编译好的dom
操作方法,没有虚拟dom
比较。
用法
TS 定义
js
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
设置为函数,根据新值和旧值的关系来设置何时为响应式。
使用
js
import { createSignal, createEffect } from "solid-js";
const EqualityExample = () => {
// 使用对象类型并自定义比较函数
const [user, setUser] = createSignal(
{ id: 1, name: "Alice" },
{ equals: (prev, next) => prev.id === next.id }
);
createEffect(() => {
console.log("User changed:", user().name);
});
// 测试用例:
// 1. 相同ID不触发更新
setUser({ id: 1, name: "Alice Smith" }); // 不会触发 effect
// 2. 不同ID触发更新
setUser({ id: 2, name: "Bob" }); // 会触发 effect
};
实现
- 在下文
createEffect
中会涉及响应式逻辑,createSignal
仅关注数据存取。
js
function createSignal<T>(
value?: T,
options?: SignalOptions<T | undefined>
): Signal<T | undefined> {
// 处理外部 equals 逻辑
options = options ? Object.assign({}, signalOptions, options) : signalOptions;
const s: SignalState<T | undefined> = {
value,
comparator: options.equals || undefined
};
const setter: Setter<T | undefined> = (value?: unknown) => {
if (typeof value === "function") {
return value = value(s.value);
}
return writeSignal(s, value);
};
return [readSignal.bind(s), setter];
}
function writeSignal(node: SignalState<any> | Memo<any>, value: any) {
// 核心比较逻辑
if (!node.comparator || !node.comparator(node.value, value)) {
node.value = value; // 直接更新值
}
return value;
}
function readSignal(this: SignalState<any> | Memo<any>) {
return this.value;
}
createEffect
从
createEffect
开始,我们将正式开始了解响应式的逻辑。用法:
createEffect
接收一个函数,监听其执行情况,createEffect
会自动订阅在执行期间读取的所有Signal
,并在Signal
值之一发生改变的时候,重新运行此函数。
用法
TS定义
js
function createEffect<Next, Init>(
fn: EffectFunction<Init | Next, Next>,
value?: Init,
options?: EffectOptions & { render?: boolean }
): void
用法
js
import { createSignal, createEffect } from "solid-js";
function CounterDemo() {
const [count, setCount] = createSignal(0);
const [unrelated, setUnrelated] = createSignal(1);
// 自动追踪依赖:当count()变化时触发
createEffect(() => {
console.log("计数器变化:", count());
});
return (
<div>
<button onClick={() => setCount(c => c + 1)}>
计数 {count()}
</button>
<button onClick={() => setUnrelated(c => c + 1)}>
无关项 {unrelated()}(不会触发effect)
</button>
</div>
);
}
原理
- 依赖收集动态性:Effect执行时通过全局
Listener
指针建立依赖关系,实现运行时动态追踪。每次执行都会重建依赖,确保精确性。 - 更新标记策略:Signal更新时通过
writeSignal
将关联Effect标记为STALE(陈旧状态),而非立即执行。采用位标记(STALE=1)实现轻量级状态管理。 - 批量更新机制:通过微任务队列(queueMicrotask)实现异步批量更新,在
runUpdates
中统一处理所有待更新Effect,避免重复计算。 - 值比较优化:通过SignalState中的
comparator
实现自定义值比较,避免不必要的更新传播。 - 分层调度控制:通过EffectOptions中的
render
标记区分渲染阶段/非渲染阶段Effect,实现不同优先级的更新调度。 - 状态位运算:使用数字状态位(STALE=1, PENDING=2)代替布尔值,支持多状态组合判断,在
updateComputation
中实现高效状态流转。 - 内存安全机制:每次执行Effect前调用
cleanNode
清除旧依赖,防止内存泄漏。采用双向链表(observers/sources)实现快速依赖解绑。
Solidjs 中有一个 Listener,这个东西相当于全局的以及最新的 Effect

实现
js
// 全局状态
let Listener: EffectFn | null = null;
const Updates = new Set<EffectFn>();
// Signal 结构
interface Signal<T> {
value: T;
observers: Set<EffectFn>;
comparator?: (a: T, b: T) => boolean;
}
// Effect 结构
interface EffectFn {
(): void;
state: number;
sources: Signal<any>[];
}
function createSignal<T>(
value?: T,
options?: SignalOptions<T | undefined>
): Signal<T | undefined> {
// 处理外部 equals 逻辑
options = options ? Object.assign({}, signalOptions, options) : signalOptions;
const s: SignalState<T | undefined> = {
value,
observers: new Set(),
comparator: options.equals || undefined
};
const setter: Setter<T | undefined> = (value?: unknown) => {
if (typeof value === "function") {
else value = value(s.value);
}
return writeSignal(s, value);
};
return [readSignal.bind(s), setter];
}
function readSignal<T>(signal: Signal<T>) {
if (Listener) {
signal.observers.add(Listener);
Listener.sources.push(signal);
}
return signal.value;
}
function writeSignal<T>(signal: Signal<T>, value: T) {
if (!signal.comparator!(signal.value, value)) {
signal.value = value;
signal.observers.forEach(effect => {
effect.state = STALE;
Updates.add(effect);
});
scheduleUpdate();
}
return value;
}
function createEffect(fn: () => void) {
const effect: EffectFn = () => {
cleanNode(effect); // 清理旧依赖
Listener = effect;
fn();
Listener = null;
};
effect.state = STALE;
effect.sources = [];
effect();
}
function cleanNode(effect: EffectFn) {
effect.sources.forEach(signal => {
signal.observers.delete(effect);
});
effect.sources.length = 0;
}
// 调度更新
const STALE = 1;
function scheduleUpdate() {
if (!updateScheduled) {
queueMicrotask(() => {
Updates.forEach(effect => {
if (effect.state === STALE) {
effect();
}
});
Updates.clear();
});
updateScheduled = true;
}
}
let updateScheduled = false;
batch
用法
Solid 的 batch
工具函数允许将多个更改推入队列,然后在通知观察者之前同时使用它们。在批处理中更新的信号值直到批处理完成才会提交。
js
import { render } from "solid-js/web";
import { createSignal, batch } from "solid-js";
const App = () => {
const [firstName, setFirstName] = createSignal("John");
const [lastName, setLastName] = createSignal("Smith");
const fullName = () => {
console.log("Running FullName");
return `${firstName()} ${lastName()}`
}
const updateNames = () => {
console.log("Button Clicked");
batch(() => {
setFirstName(firstName() + "n");
setLastName(lastName() + "!");
})
}
return <button onClick={updateNames}>My name is {fullName()}</button>
};
render(App, document.getElementById("app"));
实现
js
export function batch<T>(fn: Accessor<T>): T {
return runUpdates(fn, false) as T; // ① 入口封装
}
function runUpdates<T>(fn: () => T, init: boolean) {
if (Updates) return fn(); // ② 嵌套批处理优化
if (!init) Updates = []; // ③ 初始化更新队列
ExecCount++; // ④ 事务计数器
const res = fn();
completeUpdates(wait); // ⑤ 完成批处理
}
function completeUpdates(wait: boolean) {
if (Updates) runQueue(Updates); // ⑥ 执行计算更新
const e = Effects!;
if (e.length) runUpdates(() => runEffects(e), false); // ⑦ 延迟副作用
}
untrack
在 solidjs 中,Listener 代表的是核心依赖追踪器,在 createEffect 的时候进行赋值,在 readSignal 的时候进行取用。
在 插件 中,evalContext 代表的是核心依赖追踪器,在 createEffect 的时候进行赋值,在 readSignal 的时候进行取用。
两者的执行非常类似,只是封装逻辑不一样。
用法
js
import { render } from "solid-js/web";
import { createSignal, createEffect, untrack } from "solid-js";
const App = () => {
const [a, setA] = createSignal(1);
const [b, setB] = createSignal(1);
createEffect(() => {
console.log(a(), untrack(b));
});
return <>
<button onClick={() => setA(a() + 1)}>Increment A</button>
<button onClick={() => setB(b() + 1)}>Increment B</button>
</>
};
render(App, document.getElementById("app"));
原理
不收集依赖地执行方法。
实现
js
export function untrack<T>(fn: Accessor<T>): T {
const listener = Listener;
Listener = null;
try {
return fn();
} finally {
Listener = listener;
}
}
on
Solid 提供一个
on
工具函数,可以为我们的计算设置显式依赖。这主要用来更明确地简洁地声明跟踪哪些信号。然而,它也允许计算不立即执行而只在第一次更改时运行。可以使用defer
选项启用此功能。
用法
js
import { render } from "solid-js/web";
import { createSignal, createEffect, on } from "solid-js";
const App = () => {
const [a, setA] = createSignal(1);
const [b, setB] = createSignal(1);
createEffect(on(a, (a) => {
console.log(a, b());
}, { defer: true }));
return <>
<button onClick={() => setA(a() + 1)}>Increment A</button>
<button onClick={() => setB(b() + 1)}>Increment B</button>
</>
};
render(App, document.getElementById("app"));
原理

- 依赖快照 :在effect执行瞬间捕获依赖值,避免后续依赖变更影响当前计算
- 双缓存策略 :通过 prevInput 保留前次输入,实现差分比较优化
- 批处理更新 :与
batch
协同实现批量更新 - 惰性求值 : defer 选项延迟计算到下一个微任务周期
实现
js
export function on<S, Next extends Prev, Prev = Next>(
deps: AccessorArray<S> | Accessor<S>,
fn: OnEffectFunction<S, undefined | NoInfer<Prev>, Next>,
options?: OnOptions
): EffectFunction<undefined | NoInfer<Next>> {
// 1. 判断依赖类型(数组/单个)
const isArray = Array.isArray(deps);
let prevInput: S;
let defer = options && options.defer;
// 2. 返回可被createEffect使用的effect函数
return prevValue => {
let input: S;
// 3. 收集依赖的最新值
if (isArray) {
input = Array(deps.length) as unknown as S;
for (let i = 0; i < deps.length; i++)
(input as unknown as TODO[])[i] = deps[i]();
} else {
input = deps();
}
// 4. 处理延迟执行逻辑
if (defer) {
defer = false;
return prevValue;
}
// 5. 在untrack上下文中执行用户函数
const result = untrack(() => fn(input, prevInput, prevValue));
// 6. 保存当前输入作为下次的prevInput
prevInput = input;
return result;
};
}
插件的实现
js
export function on<S, Next extends Prev, Prev = Next>(
deps: SignalGetter<S>[] | SignalGetter<S>,
fn: OnEffectFunction<S, undefined | NoInfer<Prev>, Next>,
options?: OnOptions,
): EffectFunction<undefined | NoInfer<Next>> {
let prevInput: S;
let defer = options?.defer;
return (prevValue) => {
let input: S;
if (Array.isArray(deps)) {
input = (Array(deps.length) as unknown) as S;
for (let i = 0; i < deps.length; i++) {
((input as unknown) as any[])[i] = deps[i]();
}
} else {
input = deps();
}
if (defer) {
defer = false;
return undefined;
}
const result = untrack(() => fn(input, prevInput, prevValue));
prevInput = input;
return result;
};
}
渲染 & 编译
模版编译
源代码
js
import { render } from 'solid-js/web';
function HelloWorld() {
const name = 'world';
return <div>Hello {name}!</div>;
}
render(() => <HelloWorld />, document.getElementById('app'))
编译后代码
js
import { template as _$template } from "solid-js/web";
import { createComponent as _$createComponent } from "solid-js/web";
const _tmpl$ = /*#__PURE__*/_$template(`<div>Hello world!</div>`, 2);
import { render } from 'solid-js/web';
function HelloWorld() {
const name = 'world';
return _tmpl$.cloneNode(true);
}
render(() => _$createComponent(HelloWorld, {}), document.getElementById('app'));
编译时优化 :
- 将 JSX 模板编译为真实的 DOM 创建指令
- 静态内容预先生成为克隆模板
- 动态部分通过 insert() 等指令进行标记
看看 template 实现
js
export function template(html, check, isSVG) {
const t = document.createElement("template");
t.innerHTML = html;
// ...一些代码
let node = t.content.firstChild;
if (isSVG) node = node.firstChild;
return node;
}
可以看到,name
在编译时已经被插入了。
再看看 createComponent 实现
js
export function createComponent<T>(Comp: Component<T>, props: T): JSX.Element {
// ...一些代码
return untrack(() => Comp(props || ({} as T)));
}
响应式对 DOM 更新的影响
- 每个 DOM 绑定对应独立的 effect
- 状态变更时通过响应式系统直接定位到需要更新的 DOM 节点
- 无需虚拟 DOM diff,直接操作真实 DOM
