-
-
Notifications
You must be signed in to change notification settings - Fork 1
rikka signal
@takanashi/rikka-signal 基于 TC39 Signals 提案 与 signal-polyfill 实现,提供细粒度响应式能力。
与 Vue / React / Svelte 等框架各自拥有响应式实现不同,Rikka 的 signal() 直接构建在正在成为 Web 标准的提案之上。当浏览器原生实现该提案时,Rikka 只需:
- 移除
signal-polyfill依赖 - 保留 API 不变
即获得原生性能和更小体积,用户代码无需任何改动。
| 函数 | 说明 | 位置 |
|---|---|---|
signal<T>(initial: T): Signal.State<T> |
创建可变信号 | src/index.ts |
computed<T>(fn: () => T): Signal.Computed<T> |
创建派生(计算)信号 | src/index.ts |
| `effect(fn: () => void | (()=>void)): () => void` | 创建副作用,返回清理函数 |
untracked<T>(fn: () => T): T |
读取信号但不追踪 | src/index.ts |
Signal 命名空间 |
底层 polyfill 原语,Signal.State / Signal.Computed / Signal.subtle.Watcher
|
(re-export) |
创建一个响应式状态容器。使用 .get() 读取,.set(v) 更新。
const count = signal(0);
console.log(count.get()); // 0
count.set(5);
console.log(count.get()); // 5内部实现:直接代理
new Signal.State<T>(initialValue)。Signal.State维护一个版本号,每次.set()递增,订阅者感知变化并在下一微任务运行。
创建一个派生信号,它的值由其他信号 fn() 自动计算得到。computed 是惰性的:只有在 .get() 时才计算,且会缓存结果直到依赖变化。
const a = signal(1);
const b = signal(2);
const sum = computed(() => a.get() + b.get());
console.log(sum.get()); // 3
a.set(10);
console.log(sum.get()); // 12在响应式上下文中执行 fn,自动追踪其读取的信号,当任一信号变化时重新执行。fn 可以返回一个清理函数,用于下一次执行前或 effect 销毁时清理资源。
const dispose = effect(() => {
console.log("count =", count.get());
return () => console.log("cleanup");
});
count.set(1); // logs: cleanup, count = 1
count.set(2); // logs: cleanup, count = 2
dispose(); // logs: cleanup (最终)
count.set(3); // 无输出 (已注销)内部实现:
effect通过Signal.subtle.Watcher注册到 polyfill。每次执行时,它包装Signal.Computed用于依赖追踪,并用queueMicrotask异步调度。通过disposed标志防止销毁后仍被调度。effect.ts还提供onError重载以捕获执行期错误。
effect(() => {
// 订阅 log,不订阅 count
const snapshot = untracked(() => count.get());
console.log(log.get(), "count was", snapshot);
});设计意图:某些副作用只关心「触发条件」信号而不关心「数据读取」信号时,
untracked可以降低响应面,减少不必要的重跑。
const count = signal(0);
count.set(count.get() + 1);const todos = signal<Todo[]>([]);
const doneCount = computed(() =>
todos.get().filter(t => t.done).length
);const show = signal(true);
const a = signal(10);
effect(() => {
if (show.get()) {
console.log("a =", a.get()); // 仅当 show 为 true 时追踪 a
} else {
console.log("hidden"); // 不再追踪 a
}
});依赖关系会随着代码路径而动态建立 / 解除——这是 Signals 相对传统观察者模式的关键优势。
rikka-dom 的 h() / tag helpers / applyChild() 接受 Signal.State<T> / Signal.Computed<T> 作为 children 和大部分属性值(text、style、value、checked 等)。
当信号变化时,只有对应节点被更新,不会重建整个子树:
import { div, button } from "@takanashi/rikka-dom";
import { signal } from "@takanashi/rikka-signal";
const count = signal(0);
div(
button({ onclick: () => count.set(count.get() + 1) }, "+"),
" count = ", count, // 仅更新文本节点
);- 不要在 effect 内直接 set 订阅中的信号 — 可能导致无限循环
-
保持
fn纯 —computed(fn)的fn不应有副作用 - 避免循环依赖 — A 依赖 B 且 B 依赖 A 会导致求值顺序问题
-
显式清理 effect — 组件销毁时调用返回的
dispose()
-
utils/rikka-signal/src/index.ts— 主 API -
utils/rikka-signal/src/effect.ts— 带清理的 effect 实现 -
utils/rikka-signal/test/index.test.ts— 单元测试
- TC39 Signals Proposal explainer
- rikka-dom API — 如何在 DOM 中使用信号
- rikka-elements API — 如何在自定义元素中使用信号
Rikka Wiki
- Home
- Getting Started
- Architecture Overview
- Core Packages
- Components
- Examples & Patterns
- Development & CI
- For LLMs and Agents
External links