Svelte 5 中的 Stores 深入解析:Store 契约、svelte/store 实现原理与实战指南
发布时间:2026/9/5 23:04:02来源:尧图网络
Svelte 5 中的 Stores 深入解析Store 契约、svelte/store 实现原理与实战指南【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte本篇基于 Svelte 仓库官方文档 documentation/docs/06-runtime/01-stores.md 展开系统讲解 store存储的核心概念如何通过$前缀在组件中自动订阅、svelte/store模块提供的writable/readable/derived/readonly/get各方法及其完整用法、自定义 store 必须满足的 store 契约并结合 svelte/store 共享实现 的源码剖析订阅去重、批处理队列与derived位掩码机制的底层原理帮助你在 Svelte 5 的 runes 时代准确判断何时该用 store、如何写出符合契约的自定义 store。什么是 Store$前缀的自动订阅机制一个store是通过简单的store 契约store contract允许对值进行响应式访问的对象。svelte/store模块 提供了满足该契约的最小 store 实现。Store 最关键的开发者体验特性是$前缀自动订阅只要你在组件中持有一个 store 的引用就可以在其前面加$字符直接访问它的当前值。Svelte 编译器会替你声明这个带前缀的变量在组件初始化时订阅该 store并在组件销毁或不再使用时自动取消订阅。对$前缀变量赋值要求该变量是一个writable store赋值会被编译器转换为对 store.set方法的调用。注意store 必须声明在组件的顶层——不能放在if块或函数内部。局部变量不代表 store 值的变量不得使用$前缀避免与自动订阅语法冲突。官方文档给出的最小示例script import { writable } from svelte/store; const count writable(0); console.log($count); // logs 0 count.set(1); console.log($count); // logs 1 $count 2; console.log($count); // logs 2 /script这段代码演示了三种访问/修改方式读取$count、通过count.set(1)更新、直接给$count赋值即隐式.set。何时使用 StoresSvelte 5 时代的定位这是官方文档中非常重要、但在许多旧资料中缺失的指南。在 Svelte 5 之前stores 是创建跨组件响应式状态或抽取逻辑的默认方案引入 runes 之后这些用例已经大大减少抽取逻辑更好利用 runes 的通用响应性——runes 可以脱离组件顶层使用甚至可以放进普通 JavaScript/TypeScript 文件使用.svelte.js或.svelte.ts文件扩展名。共享状态可以直接创建一个包含所需字段的$state对象然后直接操作该状态。/// file: state.svelte.js export const userState $state({ name: name, /* ... */ });!--- file: App.svelte --- script import { userState } from ./state.svelte.js; /script pUser name: {userState.name}/p button onclick{() { userState.name new name; }} change name /button因此stores 仍然适用的场景收窄为复杂的异步数据流或者需要更手动地控制值的更新/变更监听时。如果你熟悉 RxJS 并希望复用相关经验store 的$前缀订阅语法同样非常顺手。svelte/store 模块五个核心 APIsvelte/store提供了一个最小的 store 实现覆盖三类创建方式可从外部更新的 store、只能从内部更新的 store以及组合/派生 store 的工具函数。writable可外部写入的 storewritable(value, start?)创建一个值可以从组件外部设置的 store返回带set与update方法的对象set(value)接收一个参数即要设置的值。只有当 store 当前值与传入值不相等源码中为safe_not_equal比较时才更新并通知订阅者。update(fn)接收一个回调回调以现有 store 值为参数返回新值写入 store。/// file: store.js import { writable } from svelte/store; const count writable(0); count.subscribe((value) { console.log(value); }); // logs 0 count.set(1); // logs 1 count.update((n) n 1); // logs 2第二个参数startStartStopNotifier——延迟/外部数据源接入的关键。如果传入函数它会在订阅者数量从 0 变为 1时被调用而不是 1 变 2。该函数收到两个参数一个set函数设置 store 值和一个update函数与 store 上的update方法相同。它必须返回一个stop函数在订阅者数量从 1 变回 0 时被调用。/// file: store.js import { writable } from svelte/store; const count writable(0, () { console.log(got a subscriber); return () console.log(no more subscribers); }); count.set(1); // does nothing const unsubscribe count.subscribe((value) { console.log(value); }); // logs got a subscriber, then 1 unsubscribe(); // logs no more subscribers注意两个细节无任何订阅者时set(1)不会产生可见效果start未运行值更新被跳过writable的值在 store 销毁如页面刷新后会丢失若需持久化需自行实现例如把值同步到localStorage。源码印证在 packages/svelte/src/store/shared/index.js 中writable内部用Set保存订阅者subscribers.size 1时才调用start(set, update)并保存返回的stop取消订阅后size 0时调用stop()。set的通知采用subscriber_queue批处理只有当队列为空说明不是嵌套触发时才 flush 队列这使得在一次同步代码中多次set时各订阅者回调按序集中执行避免交错通知。readable只能内部更新的 storereadable创建一个值不能从外部设置的 store第一个参数是初始值第二个参数与writable的start参数相同——适合封装定时器、Web API 等自驱动的数据源。import { readable } from svelte/store; const time readable(new Date(), (set) { set(new Date()); const interval setInterval(() { set(new Date()); }, 1000); return () clearInterval(interval); }); const ticktock readable(tick, (set, update) { const interval setInterval(() { update((sound) (sound tick ? tock : tick)); }, 1000); return () clearInterval(interval); });time展示了用set直接推入最新值ticktock展示了用update基于当前值做变换。从源码看readable 的实现 非常薄它直接返回writable(value, start).subscribe包装成的对象即readable在运行时就是剥离了set/update的 writable。derived从一个或多个 store 派生derived(stores, fn, initial_value?)从一到多个 store 派生新 store。回调在第一个订阅者订阅时运行之后每当依赖 store 变化时重新运行。最简形式单个 store 回调返回派生值import { type Writable } from svelte/store; import { derived } from svelte/store; declare const a: Writablenumber; const doubled derived(a, ($a) $a * 2);异步形式回调接收第二个参数set可选第三个参数update可以异步调用它们设置派生值。此时还可以给derived传第三个参数作为初始值——在set/update首次被调用前派生 store 的值就是它若不指定初始值为undefined。import { derived } from svelte/store; const delayed derived( a, ($a, set) { setTimeout(() set($a), 1000); }, 2000 ); const delayedIncrement derived(a, ($a, set, update) { set($a); setTimeout(() update((x) x 1), 1000); // every time $a produces a value, this produces two // values, $a immediately and then $a 1 a second later });cleanup 形式如果回调返回一个函数它会在两种时机被调用a) 回调再次运行之前b) 最后一个订阅者取消订阅时。这是把setInterval/WebSocket 等副作用接入派生 store 的标准方式import { derived } from svelte/store; const tick derived( frequency, ($frequency, set) { const interval setInterval(() { set(Date.now()); }, 1000 / $frequency); return () { clearInterval(interval); }; }, 2000 );多 store 形式两种情况下第一个参数都可以传 store 数组代替单个 store回调收到对应的值数组import { derived } from svelte/store; const summed derived([a, b], ([$a, $b]) $a $b); const delayed derived([a, b], ([$a, $b], set) { setTimeout(() set($a $b), 1000); });源码印证derived 的实现 有几个值得注意的细节它通过fn.length 2回调声明参数个数判断是自动派生返回值即新值还是手动 set模式而不是靠返回值是否为函数区分——所以同步模式返回普通值、异步模式返回 cleanup 函数才能被正确识别每个上游 store 的回调里用位掩码pending记录哪些依赖尚未就绪多个依赖在同一 tick 内变化时sync()只会在所有依赖都更新完毕后执行一次回调返回的函数被保存为cleanup在下次sync或停止时调用这正是文档所述回调重跑前或最后一个订阅者离开时的实现。readonly把 store 变成只读readonly(store)返回一个新的只读 store仍然可以订阅原始 store 的所有变化但类型上不再暴露setimport { readonly, writable } from svelte/store; const writableStore writable(1); const readableStore readonly(writableStore); readableStore.subscribe(console.log); writableStore.set(2); // console: 2 // errors: 2339 readableStore.set(2); // ERROR实现上它只是把store.subscribe重新绑定后返回一个仅含subscribe的对象见源码。get不订阅时读取当前值一般应当通过订阅 store、随时间使用其值来读取。但偶尔你需要读取一个当前并未订阅的 store 的值get就是为此提供注意get的实现是创建订阅 → 读取值 → 立即取消订阅因此不建议在热点代码路径中使用。import { get } from svelte/store; const value get(store);对应源码只有一行核心逻辑subscribe_to_store(store, (_) (value _))()即订阅回调在同步调用中捕获值随即调用返回的 unsubscribe。Store 契约手写自定义 store 的规则不依赖svelte/store你也可以通过实现store 契约创建自己的 storestore { subscribe: (subscription: (value: any) void) (() void), set?: (value: any) void }store 必须包含.subscribe方法接受一个订阅函数作为参数。该订阅函数必须在调用.subscribe时立即、同步地以 store 当前值被调用之后每当 store 值变化所有活跃订阅函数都必须被同步调用。.subscribe必须返回一个取消订阅函数。调用它必须停止对应订阅且对应的订阅函数不得再被 store 调用。store可选地包含.set方法接受一个新值同步调用所有活跃订阅函数。含.set的 store 称为writable storewritable store 按 Writable 接口 还应提供update。与 RxJS 的互操作.subscribe也允许返回一个带.unsubscribe方法的对象而非直接返回取消函数。但注意除非.subscribe同步调用了订阅函数Observable 规范并不要求这一点否则在 Svelte 看来 store 的值会是undefined直到它被调用。仓库中的subscribe_to_store工具函数 明确处理了这一分支unsub.unsubscribe ? () unsub.unsubscribe() : unsub。源码纵深类型定义与 runes 时代的桥接 API类型层面packages/svelte/src/store/public.d.ts 定义了四个核心类型——SubscriberT(value: T) void、Unsubscriber() void、UpdaterT(value: T) T和StartStopNotifierT以及ReadableT/WritableT接口。Readable.subscribe的签名中带有可选的invalidate参数这是 Svelte 内部用于让 store 主动标记依赖失效的私有通道私有类型SubscribeInvalidateTuple把两者配对。runes 与 store 的桥接Svelte 5 在 svelte/store 客户端入口 额外提供了toStore与fromStore两个函数服务端入口 index-server.js 也有简化版本toStore(get, set?)从一个返回 state 的函数及可选的写入函数创建 store。客户端实现会把get包进render_effect使$state等 runes 值的每次变化都能推入 store——这是从 runes 世界向 store/RxJS 世界单向输出的官方通道。fromStore(store)把 store 转成带响应式current属性的对象writable 可写readable 只读。在响应式上下文中读取current会订阅 store内部使用 createSubscriber在 runes 代码里就能以熟悉的属性访问方式消费 store。这意味着即使你按官方指南优先使用 runes遗留的 store 生态含社区 store 库、RxJS 兼容对象也能通过fromStore/toStore平滑接入新的响应式体系而无需重写。小结选型速查需求推荐方案跨组件共享状态.svelte.js文件中的$state对象抽取/复用响应式逻辑runes .svelte.js/.svelte.ts文件复杂异步数据流、手动控制更新与监听writablestart/readable组合多个 storederived支持同步、异步 set、cleanup、多 store 数组对外暴露只读视图readonly一次性读取未订阅的 storeget避免热点路径对接 WebSocket/定时器等外部源实现 store 契约含 RxJS 风格的unsubscribe对象兼容参考入口概念文档 01-stores.md、实现 shared/index.js、客户端扩展 index-client.js、类型 public.d.ts、订阅桥接 utils.js 与 create-subscriber.js。【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网