个人对于sign的理解

理解 Signal:下一代前端响应式核心机制

个人对于sign的理解

一、为什么会有 Signal?

在前端响应式编程的发展历史中,我们经历了多个阶段:

  • MVC 双向绑定(AngularJS)
  • 虚拟 DOM + Diff(React、Vue2)
  • Proxy 响应式(Vue3)
  • Hooks 状态管理(React 16+)
  • Vue3.6+ 中的 alien-signal

虽然这些方案都解决了"数据驱动视图"的核心问题,但它们普遍存在渲染粒度粗 的问题:

一个状态变化,可能会导致整个组件或子树重新渲染,即使只有一个小部分依赖它。

Signal 的出现,就是为了让状态更新的粒度更细、更直接------只更新依赖它的那部分视图。


二、什么是 Signal?

一句话定义:

Signal 是一个可订阅的值容器,当值发生变化时,会通知所有依赖它的计算或副作用函数更新。

在现代框架中,Signal 通常分为三类:

  1. Writable Signal
    可写的信号,存储可变状态
  2. Computed Signal
    由其他信号派生的只读信号
  3. Effect
    副作用函数,当依赖的信号变化时执行

简单例子:

js 复制代码
import { signal, computed, effect } from 'some-signal-lib';

const count = signal(0);
const doubleCount = computed(() => count() * 2);

effect(() => {
  console.log("Count:", count(), "Double:", doubleCount());
});

count.set(1); // 自动触发 effect

三、Signal 与 Vue3 响应式的底层对比

特性 Vue3 reactive/ref Signal
实现方式 Proxy / getter getter + 订阅列表(Set)
依赖追踪方式 track / trigger(WeakMap) 显式注册到 subscribers
更新粒度 属性级别 值级别(更细)
调度机制 全局 effect 栈 + Map 存储 Signal 对象内部直接存储订阅者
应用场景 通用响应式系统 高性能组件更新 / 精细化状态管理

Vue3 的响应式是全局化的依赖管理,Signal 则更像是局部响应式容器,订阅和触发都局限在这个对象本身。

四、Signal 的实现原理

Signal 内部一般维护两个东西:

  • value:当前信号值
  • subscribers:依赖它的副作用函数集合(Set 去重)

4.1 依赖收集

当读取信号值时,如果存在当前活跃的副作用函数 ,就将它加入 subscribers。

4.2 更新通知

当信号值变化时,循环执行 subscribers 中的副作用函数。

flowchart TD A[effect 执行] --> B[读取 signal 值] B --> C[注册 effect 到 subscribers] C --> D[更新 signal 值] D --> E[遍历 subscribers] E --> F[执行 effect]

五、极简版 Signal 实现

下面的代码实现了最基本的 signal 和 effect,可直接在浏览器运行:

js 复制代码
let activeEffect = null;

function signal(initial) {
  const subscribers = new Set();
  let value = initial;

  const read = () => {
    if (activeEffect) subscribers.add(activeEffect);
    return value;
  };

  const write = (newValue) => {
    value = newValue;
    subscribers.forEach(fn => fn());
  };

  return [read, write];
}

function effect(fn) {
  activeEffect = fn;
  fn();
  activeEffect = null;
}

// 测试
const [count, setCount] = signal(0);

effect(() => {
  console.log("count changed:", count());
});

setCount(1);
setCount(2);

六、Computed Signal 的实现

在这个版本里,我们添加派生信号(computed):

js 复制代码
function computed(getter) {
  const [read, write] = signal();
  effect(() => write(getter()));
  return read;
}

// 测试
const [count, setCount] = signal(0);
const doubleCount = computed(() => count() * 2);

effect(() => {
  console.log("Double:", doubleCount());
});

setCount(3); // Double: 6

七、Signal 与 Vue3 响应式的对比

Vue3 的响应式和 Signal 的相似点:

  • 都有 副作用函数(effect)
  • 都有 依赖收集(track) 和 触发更新(trigger)

但它们的依赖存储结构和触发路径完全不同。


7.1 数据结构差异

Vue3 响应式依赖存储
js 复制代码
WeakMap (targetMap)
  -> Map (depsMap)
      -> Set (dep)
          -> effect 函数
  • targetMap 记录不同的对象

  • depsMap 记录对象的不同属性

  • dep 存储依赖该属性的所有 effect

Signal 依赖存储
js 复制代码
Signal 对象
  -> subscribers (Set)
      -> effect 函数
  • 依赖直接存在 Signal 实例上

  • 没有全局 WeakMap,也不区分属性级别

7.2 执行路径对比

Vue3 track/trigger 流程
sequenceDiagram participant User as 用户访问/修改属性 participant Proxy as Proxy 拦截 participant Track as track() participant TargetMap as targetMap participant Trigger as trigger() participant Effect as effect() User->>Proxy: get/set 属性 Proxy->>Track: 调用 track/trigger Track->>TargetMap: 查找 target TargetMap->>TargetMap: 获取/创建 depsMap TargetMap->>TargetMap: 获取/创建 dep Track->>Effect: 添加 activeEffect Trigger->>TargetMap: 获取 depsMap TargetMap->>Trigger: 获取 dep Trigger->>Effect: 执行所有依赖
Signal 订阅/触发流程
sequenceDiagram participant User as 用户读取/设置值 participant Signal as Signal 对象 participant Subs as subscribers(Set) participant Effect as effect() User->>Signal: 调用 read() Signal->>Subs: 添加 activeEffect User->>Signal: 调用 write(newValue) Signal->>Subs: 遍历 subscribers Subs->>Effect: 执行所有依赖

7.3 精简源码对比

Vue3 核心
js 复制代码
function track(target, key) {
  let depsMap = targetMap.get(target);
  if (!depsMap) targetMap.set(target, depsMap = new Map());
  let dep = depsMap.get(key);
  if (!dep) depsMap.set(key, dep = new Set());
  dep.add(activeEffect);
}

function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  const dep = depsMap.get(key);
  dep && dep.forEach(effectFn => effectFn());
}
Signal 核心
js 复制代码
function signal(initial) {
  const subscribers = new Set();
  let value = initial;

  const read = () => {
    if (activeEffect) subscribers.add(activeEffect);
    return value;
  };
  const write = (newValue) => {
    value = newValue;
    subscribers.forEach(fn => fn());
  };
  return [read, write];
}

7.4 核心差异总结

对比项 Vue3 响应式 Signal
存储结构 WeakMap → Map → Set Signal 对象 → Set
依赖收集范围 属性级别 值级别
全局性 依赖全局 targetMap 管理 每个信号独立管理
更新路径 多层查找 + 批量触发 直接触发订阅者
场景 全局状态、复杂组件树 精细化状态、局部性能优化

八、总结

  • Signal 是一种更细粒度的响应式机制
  • 相比 Vue3 的 Proxy 响应式,Signal 更新路径更短,订阅范围更小
  • 在性能敏感的场景(如大列表渲染、动画状态管理)中表现更佳
  • 越来越多的现代框架将 Signal 纳入核心
相关推荐
Jmyd01233 小时前
Web端3D营销展厅技术实现路径:从素材上云到线索回流的数据链路
前端·3d·性能优化·三维数字化
砚凝霜3 小时前
【软考信息安全】第二十二章 Web服务器安全分析与增强——Apache与IIS
服务器·前端·安全
u0111026755 小时前
JPG、PNG、WebP 怎么选?按透明度、体积和使用场景做判断
前端·javascript·vue.js·图像处理·ai作画
jerrywus5 小时前
让 AI 写完代码后交一份报告:我改造了 answer-me-with-html
前端·claude·cursor
派小心.6 小时前
小程序分享链路中来源参数的传递校验
大数据·前端·数据库·人工智能·小程序·数据分析
hhb_6186 小时前
一键生成数据看板,ECharts实战演示
前端·javascript·echarts
Java后端的Ai之路6 小时前
08_React组件API
前端·react.js·前端框架
小马不起床7 小时前
零基础玩转 FastAPI_09:前端基础工程构建
前端·状态模式·fastapi
风骏时光牛马8 小时前
用户管理模块接口开发
前端
彭亚川Allen8 小时前
从零手搓 RAG 之检索全流程,给大模型接上自己的知识库
前端·后端·ai编程