一个 *store* 是一个允许通过简单的 *store contract* 反应式访问值的对象。The [`svelte/store` 模块](../svelte-store) 包含满足此合约的最小 store 实现。
任何时间当你引用一个商店时,你都可以通过在它前面加上`$`字符来在组件内部访问其值。这会导致 Svelte 声明带前缀的变量,在组件初始化时订阅商店,并在适当的时候取消订阅。
任务分配给以`$`为前缀的变量要求该变量是一个可写存储,这将导致调用存储的`.set`方法。
请注意,存储必须在组件的最顶层声明——不能在例如 `if` 块或函数内部。
本地变量(不代表存储值)不得有 *前缀* `$`。
```svelte
```
##
何时使用商店
在 Svelte 5 之前,存储是创建跨组件响应式状态或提取逻辑的首选解决方案。随着 runes 的出现,这些用例大大减少。
* 在提取逻辑时,最好利用符文的通用反应性:您可以在组件顶层之外使用符文,甚至将它们放入 JavaScript 或 TypeScript 文件中(使用`.svelte.js`或`.svelte.ts`文件扩展名)。
* 在创建共享状态时,您可以创建一个包含所需值的 `$state` 对象,然后操作该状态
```ts
/// file: state.svelte.js
export const userState = $state({
name: 'name',
/* ... */
});
```
```svelte
User name: {userState.name}
```
商店在您拥有复杂异步数据流或需要更多手动控制更新值或监听变化时仍然是一个好解决方案。如果您熟悉 RxJs 并希望重用该知识,`$` 也会对您有所帮助。
## svelte/store
The `svelte/store` 模块包含一个最小的存储实现,满足存储合约。它提供了创建可以从外部更新的存储、只能从内部更新的存储以及合并和推导存储的方法。
###
`可写`
函数创建一个可以从'外部'组件设置值的存储。它作为一个对象创建,具有额外的`set`和`update`方法。
`set` 是一个接受一个参数的方法,该参数是要设置的值。如果存储值不等于该参数的值,则存储值将被设置为参数的值。
`update` 是一个接受一个参数的方法,该参数是一个回调函数。回调函数接受现有的存储值作为其参数,并返回要设置到存储中的新值。
```js
/// 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'
```
如果将一个函数作为第二个参数传递,那么当订阅者数量从零变为一时(但不从一变为二等),该函数将被调用。该函数将传递一个更改存储值的 `set` 函数和一个类似于存储上的 `update` 方法的 `update` 函数,该函数接受一个回调来计算存储的新值。它必须返回一个在订阅者数量从一变为零时被调用的 `stop` 函数。
```js
/// 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'
```
请注意,当销毁时(例如页面刷新时),`可写`的值会丢失。然而,您可以编写自己的逻辑将值同步到例如`localStorage`。
###
`可读的`
创建一个无法从'外部'设置值的存储,第一个参数是存储的初始值,第二个参数到`可读`与第二个参数到`可写`相同。
```ts
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);
});
```
###
`派生`
从一个或多个其他存储中派生出一个存储。回调在第一个订阅者订阅时首次运行,并在存储依赖项更改时运行。
在最简单的版本中,`derived` 接收单个存储,回调返回一个派生值。
```ts
// @filename: ambient.d.ts
import { type Writable } from 'svelte/store';
declare global {
const a: Writable;
}
export {};
// @filename: index.ts
// ---cut---
import { derived } from 'svelte/store';
const doubled = derived(a, ($a) => $a * 2);
```
回调函数可以通过接受第二个参数 `设置` 和可选的第三个参数 `更新`,在适当的时候调用其中一个或两个,以异步方式设置一个值。
在这种情况下,您也可以向`derived`传递第三个参数——在首次调用`set`或`update`之前,派生存储的初始值。如果没有指定初始值,存储的初始值将是`undefined`。
```ts
// @filename: ambient.d.ts
import { type Writable } from 'svelte/store';
declare global {
const a: Writable;
}
export {};
// @filename: index.ts
// @errors: 18046 2769 7006
// ---cut---
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
});
```
如果您从回调中返回一个函数,它将在以下情况下被调用:a) 回调再次运行时,或 b) 最后一个订阅者取消订阅时。
```ts
// @filename: ambient.d.ts
import { type Writable } from 'svelte/store';
declare global {
const frequency: Writable;
}
export {};
// @filename: index.ts
// ---cut---
import { derived } from 'svelte/store';
const tick = derived(
frequency,
($frequency, set) => {
const interval = setInterval(() => {
set(Date.now());
}, 1000 / $frequency);
return () => {
clearInterval(interval);
};
},
2000
);
```
在两种情况下,可以将一个参数数组作为第一个参数传递,而不是单个存储。
```ts
// @filename: ambient.d.ts
import { type Writable } from 'svelte/store';
declare global {
const a: Writable;
const b: Writable;
}
export {};
// @filename: index.ts
// ---cut---
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);
});
```
###
`只读`
这个简单的辅助函数将存储设置为只读。您仍然可以使用这个新的可读存储订阅原始存储的变化。
```js
import { 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
```
### `get`
通常,您应该通过订阅它并使用其随时间变化的价值来读取存储的值。偶尔,您可能需要检索您未订阅的存储的值。`get` 允许您这样做。
> \[注意\] 这通过创建订阅、读取值然后取消订阅来实现。因此,不建议在热代码路径中使用。
```ts
// @filename: ambient.d.ts
import { type Writable } from 'svelte/store';
declare global {
const store: Writable;
}
export {};
// @filename: index.ts
// ---cut---
import { get } from 'svelte/store';
const value = get(store);
```
##
存储合同
```ts
// @noErrors
store = { subscribe: (subscription: (value: any) => void) => (() => void), set?: (value: any) => void }
```
您可以使用自己的存储而不依赖于 [`store`](../svelte-store),通过实现 *存储合约*:
1. 一个存储必须包含一个`.subscribe`方法,该方法必须接受一个订阅函数作为其参数。此订阅函数必须在调用`.subscribe`时立即和同步地使用存储的当前值调用。存储的所有活动订阅函数必须在存储的值更改时同步调用。
2. The `.subscribe` 方法必须返回一个取消订阅函数。调用取消订阅函数必须停止其订阅,并且对应的订阅函数不得再次由商店调用。
3. 一个商店可能*可选地*包含一个`.set`方法,该方法必须接受一个新值作为其参数,并且同步调用商店的所有活动订阅函数。这种商店被称为*可写商店*。
为了与 RxJS Observables 互操作性,`.subscribe` 方法也允许返回一个包含 `.unsubscribe` 方法的对象,而不是直接返回取消订阅函数。然而请注意,除非 `.subscribe` 同步调用订阅(这不是 Observable 规范所要求的),否则 Svelte 将看到存储的值为`undefined`,直到它这样做。