技能创建器
skill-creator
创建、改进和评估自定义技能,把专业流程转化为可重复使用的 AI 能力。
store contract directly (name stores without the `
nanostores【数据与研究】Nano Stores is a tiny, framework-agnostic state manager built from many small atomic stores (atoms, maps, computed stores) with lazy subscriptions. Use when the user wants shared state in React, Preact, Vue, Svelte, Solid, Lit, Angular or vanilla JS, needs derived or async data stores, wants to share state between islands in Astro, or asks how to mig…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Nano Stores (package nanostores, currently 1.x) keeps state in many small stores instead of one big tree. The core is 351 to 844 bytes minified and brotlied depending on what you import, has no dependencies, and tree-shakes. Stores are lazy: a store has a "mount" mode while it has listeners and a "disabled" mode otherwise (it switches off one second after the last listener leaves), so network connections and timers run only while the UI needs them. Bindings exist for React, Preact, Vue, Svelte, Solid, Lit, Angular, and Alpine.js.
npm install nanostores
npm install @nanostores/react # or @nanostores/preact | vue | solid | lit | angular | svelte-runes
npm install @nanostores/query # optional: cached remote data fetching
npm install @nanostores/persistent # optional: localStorage sync across tabs
// stores/session.ts
import { atom, map, computed, onMount, batch } from "nanostores";
export const $theme = atom<"light" | "dark">("light");
export const $user = map<{ name: string; email: string; plan: "free" | "pro" }>({
name: "",
email: "",
plan: "free",
});
export const $isPro = computed($user, (user) => user.plan === "pro");
export const $greeting = computed(
[$user, $theme],
(user, theme) => `${user.name || "Guest"} (${theme} mode)`,
);
// Lazy loading: runs when the first listener appears, cleanup when the last leaves
onMount($user, () => {
fetch("/api/me").then((r) => r.json()).then((me) => $user.set(me));
return () => { /* cleanup */ };
});
$user.setKey("plan", "pro"); // change one key; listenKeys() subscribers of other keys stay quiet
$theme.set("dark");
batch(() => { // several writes, one notification round
$user.setKey("name", "Mara Lindqvist");
$theme.set("light");
});
Useful API: store.get(), store.set(), store.subscribe(cb) (calls immediately), store.listen(cb) (only on change), listenKeys(map, ["name"], cb), effect([$a, $b], cb) for side effects, batched() for computed values that update once per tick, onSet(store, ({ newValue, abort }) => ...) for validation. Values are compared with Object.is; set store.eq to a deep-equal function for objects.
import { useStore } from "@nanostores/react";
import { $user, $isPro } from "../stores/session";
export function UserProfile() {
const user = useStore($user);
const isPro = useStore($isPro);
return (
<div>
<p>{user.email}</p>
{isPro && <span className="badge">PRO</span>}
<button onClick={() => $user.setKey("plan", "pro")}>Upgrade</button>
</div>
);
}
Other frameworks: Vue useStore() from @nanostores/vue; Solid useStore() returns an accessor (profile().name); Svelte components can use the $ store contract directly (name stores without the $ prefix there), or useStore from @nanostores/svelte-runes with .current.
// stores/api.ts
import { atom } from "nanostores";
import { nanoquery } from "@nanostores/query";
export const [createFetcherStore, createMutatorStore] = nanoquery({
fetcher: (...keys: (string | number)[]) => fetch(keys.join("")).then((r) => r.json()),
});
export const $projectId = atom<string | null>(null);
export const $projects = createFetcherStore<Project[]>(["/api/projects"]);
// A null key part means "do not fetch yet"; the store refetches when $projectId changes
export const $project = createFetcherStore<Project>(["/api/projects/", $projectId]);
export const $createProject = createMutatorStore<{ name: string }>(
async ({ data, invalidate }) => {
const res = await fetch("/api/projects", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
invalidate("/api/projects"); // dropped from cache after the request resolves
return res.json();
},
);
Fetcher state is { data, error, loading }; before the first subscriber it is { loading: false }. Mutators expose mutate. Options such as dedupeTime (4 s default), cacheLifetime, revalidateOnFocus, revalidateInterval go on nanoquery() or on each fetcher store.
Set initial values on the server ($settings.set(initial)), start loading with an empty listener, then await allTasks() before rendering. In tests call keepMount(store) to run onMount, and cleanStores($user) in afterEach.
Request: "Share a shopping cart between my Astro React header and a Vue checkout island."
// src/stores/cart.ts
import { computed } from "nanostores";
import { persistentMap } from "@nanostores/persistent";
export const $cart = persistentMap<Record<string, string>>("cart:", {}); // values are strings
export const $cartCount = computed($cart, (cart) =>
Object.values(cart).reduce((sum, qty) => sum + Number(qty), 0),
);
export function addToCart(sku: string) {
$cart.setKey(sku, String(Number($cart.get()[sku] ?? 0) + 1));
}
React header: const count = useStore($cartCount); Vue checkout: const count = useStore($cartCount); Clicking "Add" in either island updates both, and other browser tabs, because persistent stores sync through localStorage.
Request: "Query the API when the search text changes, but not for empty input."
import { atom, computed } from "nanostores";
import { createFetcherStore } from "./api";
export const $query = atom("");
const $searchKey = computed($query, (q) => (q.length >= 2 ? encodeURIComponent(q) : null));
export const $results = createFetcherStore<Product[]>(["/api/products?q=", $searchKey]);
$results stays idle (loading: false, no data) until the text has two characters; then $query.set("oat milk") fetches /api/products?q=oat%20milk and caches it by key.
$ prefix (except in Svelte) and keep actions as plain exported functions next to the store; move logic out of components.useStore($computed) over selecting inside the component so only the derived value triggers renders.get() in components or during render; it does not subscribe. Use it in actions and tests.encode/decode option); do not store secrets in localStorage.null, undefined or false part is skipped; use that for conditional fetching.