首页 / Svelte 5 入门教程 / Store(writable/readable/derived)

Svelte 5 入门教程

Store(writable/readable/derived)

本教程共 50 篇 · 第 32 篇 · 更新于 2026-08-05 · 约 7 分钟阅读

SvelteSvelte 5Storewritablereadablederived状态管理

本节目标:理解 Store 的概念和契约,学会使用 writable、readable、derived 三种 Store,掌握 $ 前缀自动订阅和 get() 手动读取。

Store 是什么

Store 是 Svelte 提供的一种状态管理模式。它本质上是一个对象,通过简单的”订阅-通知”机制让多个组件共享同一份数据。

打个比方,Store 就像报纸订阅服务。你(组件)订阅了某份报纸(Store),报社每次出新刊(值变化)就会送一份到你家。你不看了就退订(取消订阅),报社也不白送了。

在 Svelte 5 中,Runes($state 等)已经能解决大部分状态管理需求。但 Store 在处理异步数据流、需要精细控制订阅和通知的场景仍然有用。SvelteKit 内部也大量使用 Store。

Note

在 Svelte 5 中,简单的跨组件状态共享推荐用 .svelte.js 文件中的 $state。Store 更适合异步数据流、RxJS 集成等复杂场景。

Store 契约

一个对象要成为 Store,只需要满足一个条件:有一个 subscribe 方法。

store = {
	subscribe: (callback: (value: any) => void) => () => void,
	set?: (value: any) => void
}
  • subscribe 方法接收一个回调函数,立即用当前值调用一次,之后每次值变化时再调用。返回一个取消订阅的函数。
  • set 是可选的。有 set 方法的 Store 叫”可写 Store”,没有的叫”只读 Store”。

$ 前缀自动订阅

在 Svelte 组件中,你可以用 $ 前缀来访问 Store 的值。Svelte 会自动帮你订阅和取消订阅。

<script>
	import { writable } from 'svelte/store';

	const count = writable(0);
</script>

<!-- $count 自动订阅 count Store 的值 -->
<p>当前计数:{$count}</p>

<!-- 对 $count 赋值相当于调用 count.set() -->
<button onclick={() => $count += 1}>加一</button>
Note

$ 前缀只能在组件顶层使用。不能在 if 块或函数内部声明带 $ 前缀的变量。局部变量(非 Store 值)不能用 $ 前缀。

writable 可写 Store

writable 创建一个可以从外部设置的 Store。

import { writable } from 'svelte/store';

const count = writable(0);

// 订阅
const unsubscribe = count.subscribe((value) => {
	console.log(value); // 立即输出 0
});

// 设置新值
count.set(1); // 输出 1

// 基于当前值更新
count.update((n) => n + 1); // 输出 2

// 取消订阅
unsubscribe();

三个方法

方法说明
subscribe(fn)订阅值变化,返回取消订阅函数
set(value)直接设置新值
update(fn)接收回调,用返回值更新 Store

启动和停止函数

writable 的第二个参数是一个函数,在第一个订阅者出现时调用,最后一个订阅者离开时清理。

import { writable } from 'svelte/store';

const count = writable(0, () => {
	console.log('有人订阅了');

	return () => {
		console.log('没人订阅了');
	};
});

// 此时还没有订阅者,启动函数不执行
count.set(1); // 什么都不输出

const unsub = count.subscribe((v) => console.log(v));
// 输出:"有人订阅了",然后输出 1

unsub();
// 输出:"没人订阅了"
Tip

启动函数非常适合用来设置定时器、WebSocket 连接等——只有有人需要数据时才建立连接,没人了就断开,节省资源。

实战:带本地存储的计数器

// counter.js
import { writable } from 'svelte/store';

function createCounter() {
	const stored = localStorage.getItem('count');
	const { subscribe, set, update } = writable(stored ? parseInt(stored) : 0);

	return {
		subscribe,
		increment: () => update((n) => {
			const next = n + 1;
			localStorage.setItem('count', next);
			return next;
		}),
		reset: () => {
			localStorage.removeItem('count');
			set(0);
		}
	};
}

export const counter = createCounter();
<script>
	import { counter } from './counter.js';
</script>

<p>计数:{$counter}</p>
<button onclick={() => counter.increment()}>加一</button>
<button onclick={() => counter.reset()}>重置</button>

readable 只读 Store

readable 创建一个只能从内部修改的 Store。外部只能订阅,不能 setupdate

import { readable } from 'svelte/store';

const time = readable(new Date(), (set) => {
	// 启动函数:设置定时器
	set(new Date());
	const interval = setInterval(() => {
		set(new Date());
	}, 1000);

	// 返回清理函数
	return () => clearInterval(interval);
});

在组件中使用:

<script>
	import { time } from './time.js';
</script>

<p>当前时间:{$time.toLocaleTimeString()}</p>
Note

readable 的启动函数也接收 setupdate 两个参数。你可以用 update 做基于当前值的更新。第一个参数是初始值,在启动函数执行前就会被使用。

实战:tick-tock Store

import { readable } from 'svelte/store';

export const ticktock = readable('tick', (set, update) => {
	const interval = setInterval(() => {
		update((sound) => (sound === 'tick' ? 'tock' : 'tick'));
	}, 1000);

	return () => clearInterval(interval);
});

derived 派生 Store

derived 从一个或多个 Store 派生出新值。当依赖的 Store 变化时,派生 Store 自动更新。

import { derived } from 'svelte/store';

// 假设 a 是一个 writable Store
const doubled = derived(a, ($a) => $a * 2);

多 Store 派生

传入数组可以依赖多个 Store。

import { derived } from 'svelte/store';

const summed = derived([a, b], ([$a, $b]) => $a + $b);

异步派生

回调可以接收 set 参数,实现异步派生。

const delayed = derived(
	a,
	($a, set) => {
		setTimeout(() => set($a * 2), 1000);
	},
	0 // 初始值,在 set 第一次被调用前使用
);
Note

异步派生必须提供第三个参数作为初始值,否则在 set 被调用前 Store 值为 undefined

清理函数

如果派生回调返回一个函数,它会在下次回调执行或最后一个订阅者离开时调用。

const tick = derived(
	frequency,
	($frequency, set) => {
		const interval = setInterval(() => {
			set(Date.now());
		}, 1000 / $frequency);

		return () => clearInterval(interval);
	},
	0
);

get() 手动读取

有时候你不在组件内部,不能用 $ 前缀,但需要读取 Store 的当前值。用 get() 函数。

import { get } from 'svelte/store';
import { count } from './counter.js';

const value = get(count);
console.log(value);
Note

get() 的原理是订阅、读取值、立即取消订阅。它不适合在频繁调用的代码路径中使用,性能不理想。

readonly 只读包装

readonly 把一个可写 Store 包装成只读 Store。

import { readonly, writable } from 'svelte/store';

const writableStore = writable(1);
const readableStore = readonly(writableStore);

readableStore.subscribe(console.log); // 输出 1

writableStore.set(2); // 输出 2
readableStore.set(2); // 报错:readonly 没有 set 方法

Store 契约实战:自定义 Store

因为 Store 只要满足”有 subscribe 方法”就行,你可以自己实现。

// 自定义 Store:值翻倍后才通知订阅者
function createEvenStore(initial) {
	let value = initial;
	const subscribers = new Set();

	return {
		subscribe(callback) {
			callback(value);
			subscribers.add(callback);
			return () => subscribers.delete(callback);
		},
		set(newValue) {
			value = newValue;
			subscribers.forEach((cb) => cb(value));
		},
		get() {
			return value;
		}
	};
}
Tip

自定义 Store 可以跟 RxJS Observable 互操作。Svelte 的 subscribe 方法也兼容返回带 unsubscribe 方法的对象的形式。

Store vs Runes:什么时候用什么

场景推荐方案
组件内状态$state
跨组件共享简单状态.svelte.js 文件中的 $state
异步数据流(定时器、WebSocket)Store(readable
需要精细控制订阅时机Store
RxJS 集成Store(兼容 Observable)
派生计算$derivedderived
// Svelte 5 推荐的跨组件状态共享方式
// state.svelte.js
export const userState = $state({
	name: '码上学',
	loggedIn: false
});
<script>
	import { userState } from './state.svelte.js';
</script>

<p>用户名:{userState.name}</p>
<button onclick={() => userState.name = '新名字'}>
	改名
</button>

本节回顾

  • Store 是通过订阅-通知机制共享数据的对象,核心是 subscribe 方法
  • $ 前缀在组件中自动订阅 Store,赋值给 $store 等同于调用 set()
  • writable 创建可写 Store,有 setupdate 方法
  • readable 创建只读 Store,值只能从内部改变
  • derived 从其他 Store 派生新值,支持多 Store 依赖和异步派生
  • get() 手动读取 Store 当前值,但不宜频繁使用
  • Svelte 5 中简单状态推荐用 .svelte.js 中的 $state,Store 适合异步和精细控制场景