Signals:前端响应式的范式革命

# 当虚拟 DOM 成为性能瓶颈

你可能有过这样的经历:用户在搜索框快速输入,一个看似简单的搜索功能却导致了整个页面卡顿。打开 Chrome DevTools 的 Performance 面板,发现每一次 keystroke 都触发了大量组件的重新渲染——尽管 95% 的 UI 根本没有任何变化。

这不是个例。这是整个前端社区正在反思的核心问题。

2026 年,一个名为 Signals 的响应式范式正在从前端社区的核心向外辐射,影响着 React、SolidJS、Vue、Angular 等所有主流框架的设计决策。不同于以往任何一个框架特性的小修小补,Signals 代表的是一次根本性的范式转换:从 虚拟 DOM 的细粒度问题真正细粒度响应式 的跨越。

React Re-render vs Signals


# 什么是 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 Workflow


# 与传统响应式的本质区别

要理解 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 作为长期方向

Framework Adoption Timeline


# 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 实现远比上面的例子复杂。为了处理复杂的依赖图,需要考虑:

  1. 批量更新:多个 Signal 同时变化时,只执行一次更新
  2. 拓扑排序:确保 computed 按正确顺序计算
  3. 懒更新:只有被读取的 computed 才需要计算
  4. 循环检测:防止无限循环
// 更完整的批量更新实现
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 的声明式语法优势

Framework Implementation


# 对前端开发未来的影响

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 高级特性,参与框架贡献

# 值得投入的方向

  1. 响应式基础:函数式编程、订阅模式、数据流处理
  2. 编译器知识:了解 Signals 如何在编译时优化(对理解 SolidJS 很重要)
  3. 状态建模能力:在复杂的业务场景中如何设计合理的响应式图

# 结语

Signals 的崛起不是技术轮回,而是前端领域对精确性性能追求的必然进化。它提醒我们:优秀的抽象应该让复杂问题简单化,而不是用简单的原语掩盖复杂度。

对于开发者而言,这意味着角色转变:从"手动优化渲染"的工匠,变成"设计数据流动"的架构师。这不是职业危机,而是能力升级的机会。

2026 年的前端正在分叉:一条路延续虚拟 DOM 的渐进改良,另一条路走向细粒度响应式的范式革命。无论你选择哪条路,理解 Signals 的核心思想都将帮助你构建更健壮、更可维护的前端应用。


本文提到的代码示例基于 SolidJS 和 Preact Signals 的 API。如需深入学习,建议访问 solidjs.com (opens new window)@preact/signals 文档 (opens new window)