Core Reference
@zhuangtai-js/core provides framework-agnostic state primitives. It has no third-party runtime dependencies and no hidden scheduling.
Install
Section titled “Install”pnpm add @zhuangtai-js/coreatom()
Section titled “atom()”Create readable, writable, watchable state.
import { atom } from "@zhuangtai-js/core";
const count = atom(0);
count.get();count.set(1);count.set((value) => value + 1);count.watch((value, prevValue) => {});Return the current value.
const value = count.get();set(nextValue)
Section titled “set(nextValue)”Update the value immediately. nextValue can be a value or an updater function.
count.set(1);count.set((value) => value + 1);Function values are not supported as atom values: set(fn) treats fn as an updater, and passing a function type when defining an atom is a type error. To store a function, wrap it in an object, e.g. atom({ fn }).
const fnAtom = atom({ fn: () => {} });const nextFn = () => {};
fnAtom.set({ fn: nextFn });watch(callback)
Section titled “watch(callback)”Register a synchronous watcher and call it once immediately with the current value. The return value stops the watcher.
const stop = count.watch((value, prevValue) => { console.log(value, prevValue);});
stop();Calling set() on an atom while that same atom is notifying watchers throws. Watchers may update other atoms, but avoid cycles.
computed()
Section titled “computed()”Derive read-only state from one or more atoms, with dependencies discovered automatically from the .get() calls made inside the derive.
import { atom, computed } from "@zhuangtai-js/core";
const count = atom(1);const double = computed(() => count.get() * 2);
double.get(); // 2const firstName = atom("Ada");const lastName = atom("Lovelace");
const fullName = computed(() => `${firstName.get()} ${lastName.get()}`);computed() calculates its initial value when created. It subscribes to sources only while watched, and get() recalculates from current source values.
computed() discovers dependencies from the .get() calls it makes during the derive. The subscription set comes from the actual reads, so declared sources can’t drift away from what the derive really uses.
Conditional dependencies switch automatically. A shape like computed(() => flag.get() ? a.get() : b.get()) unsubscribes the old branch and subscribes the new one when flag flips.
Tracking only happens inside the synchronous derive. Reads after await or inside setTimeout are not tracked, so derives should stay synchronous.
Nested computed values stay isolated. Reading inner.get() inside an outer derive makes the outer depend on inner itself, not on inner’s internal sources.
createAtom()
Section titled “createAtom()”Create an atom creator that can install plugins. The default atom() export stays unextended.
import { createAtom } from "@zhuangtai-js/core";import { persist } from "@zhuangtai-js/persist";
const atom = createAtom().use(persist);Plugins are installed on creators, not atom instances.
Core types
Section titled “Core types”@zhuangtai-js/core exports common public types:
Atom<Value>Computed<Value>ReadableAtom<Value>NextValue<Value>Watcher<Value>StopWatchAtomValue<Atom>AtomCreatorAtomCreatorPluginAtomCreatorPluginContext
Internal creator argument types are not exported from the package entrypoint.
Semantics quick reference
Section titled “Semantics quick reference”set()applies immediately.watch()runs synchronously and is called once when registered (prevValueis anundefinedsentinel). For anAtom<T | undefined>this cannot distinguish the first notification from a previous value that happened to beundefined.- Equality uses
Object.is. - Object and array updates are reference-based; use immutable updates.
- Watcher callbacks are isolated: a throwing watcher does not interrupt the current round; after all watchers run, a single error is rethrown as-is and multiple errors are rethrown in an
AggregateError. - A watcher added during notification is immediately invoked once with
(currentValue, undefined), but does not join the broadcast snapshot in progress. - A multi-source
computedis a synchronous snapshot, not a transactional consistency boundary: updating several sources one by one, or updating other sources from within a watcher, can expose intermediate combinations; keep tightly coupled values in the same atom. computedcompares derived results withObject.is; a derive that returns a new object/array every time is treated as changed and may notify repeatedly, so return a reference-stable value when you need to suppress notifications.- Plugins are idempotent by id: installing a plugin with the same id via
use()is a no-op, and plugin ids must be globally unique. - The core does not add hidden batching, deferring, debouncing, or transactions.