Signals:前端响应式的范式革命
- 作者:Bougie
- 创建于:2026-05-31
- 更新于:2026-05-31
# 当虚拟 DOM 成为性能瓶颈
你可能有过这样的经历:用户在搜索框快速输入,一个看似简单的搜索功能却导致了整个页面卡顿。打开 Chrome DevTools 的 Performance 面板,发现每一次 keystroke 都触发了大量组件的重新渲染——尽管 95% 的 UI 根本没有任何变化。
这不是个例。这是整个前端社区正在反思的核心问题。
2026 年,一个名为 Signals 的响应式范式正在从前端社区的核心向外辐射,影响着 React、SolidJS、Vue、Angular 等所有主流框架的设计决策。不同于以往任何一个框架特性的小修小补,Signals 代表的是一次根本性的范式转换:从 虚拟 DOM 的细粒度问题 到 真正细粒度响应式 的跨越。

# 什么是 Signals?
Signals 是一种响应式原语,它将状态和计算表达为一等公民的响应式值。任何 Signal 都可以被读取、派生和订阅,而框架负责追踪依赖关系并在值变化时自动更新需要更新的部分。
听起来很熟悉?React 的 useState、Vue 的 ref、Angular 的 signal() 都提供类似功能。但关键在于实现机制的本质差异。
// Signals 的核心理念:用响应式值替代虚拟 DOM diffing
import { signal, computed, effect } from '@preact/signals';
// 创建一个响应式信号
const count = signal(0);
const doubled = computed(() => count.value * 2);
// 当 count 变化时,doubled 自动重新计算
// 当 doubled 被使用(读取)时,effect 自动追踪依赖
effect(() => {
console.log(`Count: ${count.value}, Doubled: ${doubled.value}`);
});
count.value = 5; // 自动触发 effect: "Count: 5, Doubled: 10"

# 与传统响应式的本质区别
要理解 Signals 的革命性,我们需要回顾前端响应式的演进历史。
# 三代响应式模型
| 时代 | 代表 | 核心机制 | 问题 |
|---|---|---|---|
| 脏检查 | Angular 1.x | 定期扫描所有绑定 | 性能差,无法精确追踪 |
| 虚拟 DOM | React | Diff 算法比较两棵树 | 仍需遍历组件子树 |
| 细粒度响应式 | SolidJS / Signals | 直接追踪值依赖 | 边界清晰,零开销 |
# 虚拟 DOM 的隐性成本
React 的虚拟 DOM 是天才设计,但它的本质是回退机制——当状态变化时,通过 diffing 找出需要更新的最小 DOM 操作集。这看似优雅,实际上存在几个隐性成本:
// React 模式:状态变化 → 重新渲染整棵子树
function SearchResults({ query }) {
// 每次 query 变化,这个组件及其所有子组件都会重新渲染
// React 通过 memo/useMemo/useCallback 手动优化
return (
<div>
<ResultList items={filterItems(query)} />
<SearchStats count={items.length} />
</div>
);
}
// 问题:filterItems 在每次渲染时都会执行
// 必须手动 useMemo 才能避免重复计算
// 依赖声明错误会导致 bug
# Signals 的精确更新
Signals 采用拉取(Pull)与推送(Push)结合的模式,只更新确切依赖的值:
// SolidJS 模式:Signals 精确更新
import { createSignal, createMemo } from 'solid-js';
import { For } from 'solid-js/web';
function SearchResults(props: { query: string }) {
const [query, setQuery] = createSignal(props.query);
// createMemo 自动追踪依赖,仅在 query 变化时重新计算
const filteredItems = createMemo(() =>
items.filter(item => item.name.includes(query()))
);
const stats = createMemo(() => ({
count: filteredItems().length,
avg: filteredItems().reduce((sum, i) => sum + i.price, 0) / filteredItems().length
}));
return (
<div>
{/* 只有当 filteredItems 实际变化时,列表才更新 */}
<For each={filteredItems()}>
{(item) => <ResultItem item={item} />}
</For>
{/* stats 是独立的 memo,两个值可以独立更新 */}
<SearchStats
count={stats().count}
avg={stats().avg}
/>
</div>
);
}
关键差异:React 需要开发者手动优化(memo、useMemo),而 SolidJS 的细粒度响应式让优化成为编译器和运行时的默认行为。
# 为什么 2026 年突然爆发
Signals 概念并不新鲜。SolidJS 在 2019 年就展示了这种模式的可实现性。但为什么在 2026 年,它突然成为前端领域最重要的话题?
# 性能焦虑的时代背景
1. 交互复杂度的指数增长
现代 Web 应用已经不是简单的表单和列表。实时协作编辑器、数据可视化仪表盘、复杂表单验证——这些场景中,用户操作的频率远高于传统 CRUD 应用。虚拟 DOM 在高频更新场景下的性能瓶颈变得不可忽视。
2. 边缘计算的兴起
当应用需要部署在边缘节点(Cloudflare Workers、Vercel Edge Functions)时,计算资源的限制让虚拟 DOM 的 diffing 成本变得昂贵。Signals 的零成本抽象在这些环境中尤为重要。
3. 跨端一致性需求
同一套业务逻辑需要运行在 Web、Native(React Native)、Desktop(Electron)上。虚拟 DOM 是 Web 特有的抽象,而 Signals 作为纯 JavaScript 概念天然具有跨端一致性。
# SolidJS 证明可行性
SolidJS 是第一个将细粒度响应式推向生产环境的框架。它的成功证明了几个关键假设:
// SolidJS 生产案例:Shopify 的探索
// SolidStart(SolidJS 的全栈框架)在 Shopify 的部分场景中
// 将页面加载性能提升了 3-5 倍
// 核心原理:编译时将 JSX 转换为精确的 DOM 操作
const template = `<div><span>{name()}</span></div>`;
// 编译后等价于:
const template = document.createElement('div');
const span = document.createElement('span');
name$.subscribe((v) => span.textContent = v);
template.appendChild(span);
SolidJS 的成功引发了连锁反应:
- Angular 18 将 Signals 作为官方响应式原语
- Vue 4 传闻将 Signal-based reactivity 作为核心
- Preact 推出
@preact/signals作为独立库 - React 团队 在 2025 年的 roadmap 中明确将 Signals 作为长期方向

# Signals 的核心原理与实现机制
Signals 的实现依赖于几个核心概念的正确组合。理解这些概念,有助于你在实际项目中做出正确的架构决策。
# 响应式图的构建
每个 Signal 都是一个节点,它们通过依赖关系形成有向无环图(DAG)。
// 手写一个极简 Signals 实现来理解核心原理
class Signal<T> {
private value: T;
private subscribers = new Set<() => void>();
constructor(initialValue: T) {
this.value = initialValue;
}
get(): T {
// 追踪当前执行上下文中的订阅者
if (currentObserver) {
this.subscribers.add(currentObserver);
}
return this.value;
}
set(newValue: T) {
if (this.value !== newValue) {
this.value = newValue;
// 推送更新给所有订阅者
this.notify();
}
}
private notify() {
// 批量更新优化:延迟到微任务队列中执行
queueMicrotask(() => {
this.subscribers.forEach(fn => fn());
});
}
}
// 全局上下文,用于追踪当前正在建立的依赖关系
let currentObserver: (() => void) | null = null;
// 创建计算信号
function computed<T>(fn: () => T): Signal<T> {
const signal = new Signal<T>(undefined as T);
let dirty = true;
let cachedValue: T;
const evaluator = () => {
if (dirty) {
// 建立新的依赖关系
currentObserver = evaluator;
try {
cachedValue = fn();
} finally {
currentObserver = null;
}
dirty = false;
}
signal.value = cachedValue;
};
signal.get = () => {
evaluator();
return signal.value;
};
// 追踪依赖
evaluator();
return signal;
}
# 批量更新与调度
真实的 Signals 实现远比上面的例子复杂。为了处理复杂的依赖图,需要考虑:
- 批量更新:多个 Signal 同时变化时,只执行一次更新
- 拓扑排序:确保 computed 按正确顺序计算
- 懒更新:只有被读取的 computed 才需要计算
- 循环检测:防止无限循环
// 更完整的批量更新实现
class Scheduler {
private queue = new Set<Signal<any>>();
private scheduled = false;
add(signal: Signal<any>) {
this.queue.add(signal);
if (!this.scheduled) {
this.scheduled = true;
// 使用微任务实现批量更新
Promise.resolve().then(() => this.flush());
}
}
flush() {
// 按依赖顺序排序
const sorted = this.topologicalSort(this.queue);
sorted.forEach(signal => signal.evaluate());
this.queue.clear();
this.scheduled = false;
}
private topologicalSort(signals: Set<Signal<any>>): Signal<any>[] {
// 简化的拓扑排序实现
const visited = new Set<Signal<any>>();
const result: Signal<any>[] = [];
const visit = (signal: Signal<any>) => {
if (visited.has(signal)) return;
visited.add(signal);
signal.dependencies.forEach(dep => visit(dep));
result.unshift(signal);
};
signals.forEach(visit);
return result;
}
}
# 框架级实现对比
| 框架 | Signal 实现 | 特点 |
|---|---|---|
| SolidJS | 编译器转换 JSX | 零运行时开销,性能最优 |
| Preact Signals | 独立的响应式库 | 可与 React 混用,但有限制 |
| Angular | Zone.js + 响应式图 | 与现有变更检测兼容 |
| Vue (传闻) | Proxy-based | 保持 Vue 的声明式语法优势 |

# 对前端开发未来的影响
Signals 不仅仅是一个性能优化,它将深刻改变前端的开发模式和开发者角色。
# 从"组件驱动"到"状态驱动"
React 的范式是组件优先:先组织 UI 结构,然后通过 props/hooks 注入状态。Signals 的范式是状态优先:先定义数据模型,然后让 UI 自动跟随状态更新。
// 状态优先的设计模式
import { signal, computed } from 'angular signals';
// 业务逻辑层:纯数据转换
const cartItems = signal<CartItem[]>([]);
const shippingOption = signal<ShippingOption>('standard');
const taxRate = signal(0.08);
const subtotal = computed(() =>
cartItems().reduce((sum, item) => sum + item.price * item.quantity, 0)
);
const shippingCost = computed(() => {
switch (shippingOption()) {
case 'standard': return subtotal() > 100 ? 0 : 10;
case 'express': return 25;
case 'overnight': return 50;
}
});
const tax = computed(() => subtotal() * taxRate());
const total = computed(() => subtotal() + shippingCost() + tax());
// 视图层:声明式绑定,不再需要 useMemo/useCallback
// Angular template 自动追踪依赖
// <div>{{ total() | currency }}</div>
# 开发者角色转变
传统前端开发者 的核心技能:
- 组件设计模式
- Props drilling vs Context vs Redux
- 性能优化:memo、useMemo、useCallback
- 虚拟 DOM diffing 心理模型
Signals 时代开发者 的核心技能:
- 数据流建模
- 响应式依赖图设计
- 批量更新优化
- 跨端抽象层设计
这意味着 状态管理复杂度下移:以前需要 Redux/Zustand 处理的复杂状态逻辑,现在可以自然地用 Signals 表达,而框架负责处理性能优化。
# 新一代开发工具
Signals 也催生了新的开发工具范式:
// Signals 驱动的开发工具链
// 1. 状态时间旅行调试
import { createTimeTravel } from '@signals/devtools';
const timeTravel = createTimeTravel({
cartItems,
shippingOption,
taxRate
});
timeTravel.replayTo(15); // 回放第 15 个状态变更
timeTravel.exportSnapshot(); // 导出状态快照用于复现 Bug
// 2. 可视化依赖图
import { dependencyGraph } from '@signals/viz';
// 生成可交互的依赖图,展示状态如何流动
dependencyGraph visualize({
nodes: [cartItems, shippingOption, subtotal, total],
edges: [
{ from: cartItems, to: subtotal },
{ from: shippingOption, to: shippingCost },
{ from: subtotal, to: shippingCost },
{ from: subtotal, to: total }
]
});
# 开发者应对策略
面对 Signals 浪潮,开发者应该如何准备?这里提供几点实用建议。
# 立即可行动的策略
1. 在新项目中尝试 Signals
如果你正在启动新项目,可以考虑 SolidJS 或 Preact Signals。它们已经生产就绪,且与现有生态兼容。
// SolidJS 项目初始化
// npx degit solidjs/templates/ts my-signals-app
// cd my-signals-app && npm install
// 尝试用状态优先的方式建模
import { createSignal, createEffect } from 'solid-js';
// 练习:重构一个现有的状态逻辑
// 尝试把所有状态都表达为 Signal,计算表达为 computed
2. 理解响应式思维模式
不需要立刻切换框架,但可以在任何项目中练习响应式思维:
// 练习:将命令式代码转换为声明式响应式代码
// 之前(命令式)
function handleSearch(query: string) {
const filtered = items.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
);
setFilteredItems(filtered);
setResultCount(filtered.length);
setIsEmpty(filtered.length === 0);
}
// 之后(响应式)
const query = signal('');
const filteredItems = computed(() =>
items.filter(item =>
item.name.toLowerCase().includes(query().toLowerCase())
)
);
const resultCount = computed(() => filteredItems().length);
const isEmpty = computed(() => filteredItems().length === 0);
// 状态更新变成:query.set(newQuery)
3. 关注框架演进
即使你不打算切换框架,了解 Signals 也很重要:
- React 团队正在实验性地集成 Signals 概念到 Concurrent Mode
- Angular 18+ 已将 Signals 作为官方 API
- Vue 的响应式系统已经在借鉴 Signals 的优化思路
# 中长期准备
| 时间 | 建议 |
|---|---|
| 0-6 个月 | 学习 SolidJS/Preact Signals 基础,尝试小项目 |
| 6-12 个月 | 关注框架 Signals 集成进展,开始用响应式思维建模 |
| 1-2 年 | 在适合的项目中采用 Signals,重新评估状态管理工具 |
| 2 年+ | 掌握 Signals 高级特性,参与框架贡献 |
# 值得投入的方向
- 响应式基础:函数式编程、订阅模式、数据流处理
- 编译器知识:了解 Signals 如何在编译时优化(对理解 SolidJS 很重要)
- 状态建模能力:在复杂的业务场景中如何设计合理的响应式图
# 结语
Signals 的崛起不是技术轮回,而是前端领域对精确性和性能追求的必然进化。它提醒我们:优秀的抽象应该让复杂问题简单化,而不是用简单的原语掩盖复杂度。
对于开发者而言,这意味着角色转变:从"手动优化渲染"的工匠,变成"设计数据流动"的架构师。这不是职业危机,而是能力升级的机会。
2026 年的前端正在分叉:一条路延续虚拟 DOM 的渐进改良,另一条路走向细粒度响应式的范式革命。无论你选择哪条路,理解 Signals 的核心思想都将帮助你构建更健壮、更可维护的前端应用。
本文提到的代码示例基于 SolidJS 和 Preact Signals 的 API。如需深入学习,建议访问 solidjs.com (opens new window) 和 @preact/signals 文档 (opens new window)。