@fluixi/reactive

中文文档
1.0.0-alpha.108发布于 2026/11/07MITTypeScriptESM + CJS零依赖近 30 天下载 7024

implements TC39 Signals proposal with zero runtime dependencies; observable layer for RxJS interop with signals, memos, effects, stores

原始使用文档
文档目录28 节
  1. Features
  2. Core
  3. Beyond the basics
  4. Installation
  5. Quick Start
  6. Basic Usage
  7. The tuple form
  8. Stores and resources
  9. Transitions, context and boundaries
  10. Debouncing
  11. Batching
  12. API Reference
  13. Core API
  14. Transitions
  15. Boundaries
  16. Context
  17. Other effects
  18. Observation
  19. Owner Management
  20. Lists
  21. Utilities
  22. Documentation
  23. Compiler intrinsics
  24. Performance tips
  25. Building
  26. Running Tests
  27. License
  28. Credits

Fluixi

@fluixi/reactive

Fine-grained signals, memos, effects and stores on a TC39 Signals core: standalone, no framework required.

License: MIT TypeScript npm Inspired by SolidJS


A fine-grained reactive system built on the TC39 Signals graph: glitch-free, lazy, push-pull. It has no DOM dependency and no framework dependency, so it works in the browser, on the server, or on its own.

Features

Core

  • Signals, memos, effects: fine-grained state, derived values, automatic tracking
  • Stores: nested reactive objects where reading a property is the read
  • Resources: async values that integrate with suspense
  • Batching: batch() collects writes so dependents run once

Beyond the basics

  • Transitions: startTransition defers non-critical effects so the view stays interactive
  • Suspense and error boundaries: as plain values, so any view layer can wrap them
  • Context: provide a value for the duration of a call, without prop drilling
  • Owners: explicit ownership and disposal, so a graph tears down completely
  • Debounced primitives: signal, resource and function variants
  • Observable interop: Observable/Subject for RxJS-shaped code
  • TypeScript: full type safety and inference

Installation

npm install @fluixi/reactive

Quick Start

Basic Usage

import { signal, memo, effect, batch } from '@fluixi/reactive';

// Reactive state — call it to read, .set() to write
const count = signal(0);

// Derived state (cached)
const doubled = memo(() => count() * 2);

// Side effects (auto-tracking)
effect(() => {
  console.log(`Count: ${count()}, Doubled: ${doubled()}`);
});

count.set(1); // Effect runs: "Count: 1, Doubled: 2"

// Batch multiple updates
batch(() => {
  count.set(2);
  count.set(3);
}); // Effect runs once: "Count: 3, Doubled: 6"

The tuple form

createSignal returns the same signal as a [read, write] pair, and createMemo / createEffect are memo / effect under their original names:

import { createSignal, createMemo, createEffect } from '@fluixi/reactive';

const [count, setCount] = createSignal(0);
const doubled = createMemo(() => count() * 2);
createEffect(() => console.log(count(), doubled()));
setCount(1);

signal is the accessor createSignal returns with the setter attached, so a read is the same call either way. They track each other and mix freely in one module: pick whichever reads better, and keep the tuple when you want the halves apart.

Stores and resources

import { store } from '@fluixi/reactive/store';
import { resource } from '@fluixi/reactive/signal';

const cart = store({ items: [], total: 0 });
cart.total;                 // reading a property IS the reactive read
cart.set('total', 42);      // only readers of `total` re-run

const user = resource(() => fetch('/api/me').then((r) => r.json()));
user();        // the value — suspends under a Suspense boundary
user.loading;  // boolean
user.refetch();

Transitions, context and boundaries

import {
  startTransition, useTransition,
  createContext, useContext, provide,
  createErrorBoundary, provideErrorBoundary,
} from '@fluixi/reactive';

// Non-blocking updates — `isPending` is a signal you can read while it runs
const [isPending, start] = useTransition();
start(() => setLargeDataSet(expensiveComputation()));

// Context: create it, provide a value for the duration of a call, consume inside
const ThemeContext = createContext({ mode: 'light' });
provide(ThemeContext, { mode: 'dark' }, () => {
  useContext(ThemeContext); // { mode: 'dark' }
});

// Boundaries are values here, not components. A view layer wraps these into
// <ErrorBoundary> / <Suspense>; on their own they are plain functions.
const boundary = createErrorBoundary();
provideErrorBoundary(boundary, () => risky());
boundary.error();  // the thrown value, or undefined
boundary.reset();

Debouncing

import { createDebounce, createDebouncedSignal, createDebouncedResource } from '@fluixi/reactive';

const [term, setTerm, controls] = createDebouncedSignal('', 300);
term();                // settles 300ms after the last write
controls.immediate();  // the value as written — bind the input to this one
controls.pending();    // a write is waiting (not the same as a request in flight)
controls.flush();      // settle now

// Search-as-you-type as one primitive: the query debounces, the fetch follows it,
// and an empty query doesn't fetch at all.
const [results, search] = createDebouncedResource('', (q) => fetchResults(q), { delay: 300 });
search.setQuery('fluix');
results.loading;

// Or debounce any function
const save = createDebounce((draft) => persist(draft), 500);

Batching

A write marks its dependents dirty and schedules them; memos stay lazy and recompute on read, so the graph is glitch-free: nothing observes a half-applied update.

batch(() => {
  a.set(1);
  b.set(2);
  c.set(3);
}); // dependents run once, after the batch closes

await flush(); // await the pending queue — mostly useful in tests

startTransition is the other half: it runs fn synchronously and captures the non-pure effects it would fire into a deferred queue, so an expensive update doesn't block the current view. Pure computations still settle immediately.

API Reference

Core API

  • createSignal(initialValue, options?) - Create reactive state
  • createEffect(fn, options?) - Run side effects
  • createMemo(fn, initialValue?, options?) - Create derived state
  • batch(fn) - Batch multiple updates
  • untrack(fn) - Read signals without tracking
  • createRoot(fn) - Create disposal root

Transitions

  • startTransition(fn) - Mark updates as low priority
  • useTransition() - Track transition state

Boundaries

  • createSuspenseBoundary() / provideSuspense(boundary, fn) - Async boundary as a value
  • createErrorBoundary() / provideErrorBoundary(boundary, fn) - Error boundary as a value
  • trackResourcePromise(promise) - Register a promise with the enclosing suspense

Context

  • createContext(defaultValue) - Create context
  • provide(context, value, fn) - Supply a value for the duration of a call
  • useContext(context) - Consume context

Other effects

  • createRenderEffect(fn) - Runs before paint, for DOM writes
  • createComputed(fn) - Runs eagerly, unlike a memo
  • on(deps, fn, options?) - Explicit dependencies

Observation

  • observeReactiveNodes(observer) - Watch nodes being created, recomputed and disposed. What @fluixi/devtools attaches to; unwatched, it costs one null check per node.

Owner Management

  • getOwner() - Get current reactive owner
  • runWithOwner(owner, fn) - Run with specific owner

Lists

  • mapArray(list, fn) - Map keyed by identity; a row survives a reorder
  • indexArray(list, fn) - Map keyed by index
  • keyArray(list, key, fn) - Map keyed by an explicit key

Utilities

  • createSelector(source, fn) - Keyed selector; only the row that changed re-runs
  • createResource(fetcher, source?) / resource(...) - Async resource
  • createStore(initial) / store(initial) - Nested reactive object
  • createDeferred(source, options?) - A value that trails behind under load
  • createDebounce(fn, ms) / createDebouncedSignal(initial, ms) / createDebouncedResource(...)
  • flush() - Await the pending effect queue
  • unwrap(store) / untrackStore(fn) - Escape hatches out of a store proxy
  • onCleanup(fn) / disposeScope(owner) - Teardown

Documentation

Compiler intrinsics

Inside an app compiled by @fluixi/compiler, these primitives have $-prefixed twins that need no import: $signal, $memo, $effect, $store, $resource. They compile to the calls above, so there is one reactive graph and no difference in behaviour. This package stays importable exactly as documented here, which is what a library that never runs the compiler needs.

Performance tips

  1. Batch related updates: batch(() => { a.set(1); b.set(2); }) runs dependents once.
  2. Transitions for expensive updates: startTransition(() => list.set(compute())) keeps the current view interactive while the next one is prepared.
  3. Memo the expensive part: a memo costs a node; it pays for itself when the computation is heavier than the bookkeeping.
  4. Read narrowly: reading a whole object subscribes you to every write to it. Reach for the property you need, or derive it.
  5. Key your lists: mapArray keeps a row's nodes across a reorder; indexArray rebuilds them.

Building

nx build reactive

Running Tests

nx test reactive

License

MIT

Credits

The graph implements the TC39 Signals model. The ergonomics owe a lot to prior art: SolidJS for fine-grained primitives and scheduling, React for transitions and suspense as concepts, Vue 3 for reactivity design. None of that was invented here.

版本与发布记录

@fluixi/reactive 共发布 56 个版本,首次发布于 2026/07/10;过去 12 个月发布了 56 个版本。

版本发布日期距今
1.0.0-alpha.108latest2026/11/078 天前
1.0.0-alpha.1072026/11/0511 天前
1.0.0-alpha.1062026/11/0213 天前
1.0.0-alpha.1052026/11/0115 天前
1.0.0-alpha.1042026/11/0115 天前
1.0.0-alpha.1032026/10/2620 天前

关于 @fluixi/reactive 的常见问题

@fluixi/reactive 是什么?

implements TC39 Signals proposal with zero runtime dependencies; observable layer for RxJS interop with signals, memos, effects, stores

如何安装 @fluixi/reactive?

在项目目录运行 npm install @fluixi/reactive,或使用 pnpm add / yarn add / bun add。

@fluixi/reactive 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@fluixi/reactive 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@fluixi/reactive 可以商用吗?

@fluixi/reactive 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@fluixi/reactive 的下载量有多少?

根据 npm 官方下载统计,@fluixi/reactive 在 2026/10/15 – 2026/11/13 这 30 天内被下载 7,024 次,日均约 234 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@fluixi/reactive 还在维护吗?

最新版本 1.0.0-alpha.108 发布于 2026/11/07(8 天前);至今共发布 56 个版本,其中过去 12 个月发布了 56 个;首次发布于 2026/07/10。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。