
上一讲的是生命周期,今天咱们继续开始状态管理
一、可写状态
并非所有的状态都属于在组件层次的结构内。某些时候,有些状态需要被多个毫不相干的组件或普通的 JavaScript
模块访问。
在 Svelte
中,我们使用 stores
来执行此操作。 store
只是一个带有 subscribe
方法的对象,当 store
值发生变化时,它允许相关方得到通知。 在 App.svelte
中,count
是一个 store
,我们在 count.subscribe
回调中设置 countValue
。
App.svelte
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <script> import { count } from './stores.js'; import Incrementer from './Incrementer.svelte'; import Decrementer from './Decrementer.svelte'; import Resetter from './Resetter.svelte';
let countValue;
count.subscribe(value => { countValue = value; }); </script>
<h1>The count is {countValue}</h1>
<Incrementer/> <Decrementer/> <Resetter/>
|
单击stores.js
选项卡以查看count
的定义。 它是一个可写存储,这意味着它除了订阅之外还有设置和更新方法。
stores.js
1 2 3
| import { writable } from 'svelte/store';
export const count = writable(0);
|
现在转到 Incrementer.svelte
选项卡,以便我们可以连接 +
按钮:
Incrementer.svelte
1 2 3 4 5 6 7 8 9 10 11
| <script> import { count } from './stores.js';
function increment() { count.update(n => n + 1); } </script>
<button on:click={increment}> + </button>
|
单击 +
按钮现在应该会更新计数。 对 Decrementer.svelte
执行相反的操作。
Decrementer.svelte
1 2 3 4 5 6 7 8 9 10
| <script> import { count } from './stores.js'; function decrement() { count.update(n => n - 1); } </script>
<button on:click={decrement}> - </button>
|
最后,在Resetter.svelte
中,实现reset
:
Resetter.svelte
1 2 3 4 5 6 7 8 9 10 11
| <script> import { count } from './stores.js';
function reset() { count.set(0); } </script>
<button on:click={reset}> reset </button>
|
二、自动订阅
上一个示例中的应用程序可以运行,但有一个细微的错误-store
已订阅,但从未取消订阅。 如果组件被多次实例化和销毁,这将导致内存泄漏。
首先在 App.svelte
中声明取消订阅:
App.svelte
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <script> import { count } from './stores.js'; import Incrementer from './Incrementer.svelte'; import Decrementer from './Decrementer.svelte'; import Resetter from './Resetter.svelte';
let countValue;
const unsubscribe = count.subscribe(value => { countValue = value; }); </script>
<h1>The count is {countValue}</h1>
<Incrementer/> <Decrementer/> <Resetter/>
|
调用 subscribe
方法会返回一个 unsubscribe
函数。
您现在声明了unsubscribe
,但仍需要调用它,例如通过 onDestroy
生命周期钩子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <script> import { onDestroy } from 'svelte'; import { count } from './stores.js'; import Incrementer from './Incrementer.svelte'; import Decrementer from './Decrementer.svelte'; import Resetter from './Resetter.svelte';
let countValue;
const unsubscribe = count.subscribe(value => { countValue = value; });
onDestroy(unsubscribe); </script>
|
不过,它开始有点呆板重复了,尤其是当您的组件订阅了多个存储时。Svelte
给出一个绝佳的替代方案,你可以在 store
名称前面加上$
前缀来引用这个store
的值:
1 2 3 4 5 6 7 8 9
| <script> import { count } from './stores.js'; import Incrementer from './Incrementer.svelte'; import Decrementer from './Decrementer.svelte'; import Resetter from './Resetter.svelte'; </script>
<h1>The count is {$count}</h1>
|
自动订阅仅适用于在组件的顶级范围内声明(或导入)的存储变量。
您也不局限于在标记中使用$count
,您还可以在<script>
中的任何位置使用它,例如在事件处理程序或反应式声明中。
假设以$
开头的任何名称都是指存储值。它实际上是一个保留字符-Svelte
将阻止您使用$
前缀声明自己的变量。
三、只读 stores
并非所有·stores·都需要在其他地方写入,比如,你可能有一个代表鼠标位置或者用户地理位置的stores
,这样的stores
从其他地方写入并无意义,对于这种情况,我们有 只读(readable) stores
。
点击到 stores.js
选项卡, 第一个参数 readable
可以一个是个初始值,也可以为 null
或 undefined
,第二个参数是 start
函数,该函数有个 set
回调方法,并返回一个 stop
函数。 当stores
首次被subscriber
时调用start
函数,stop
则是最后当subscriber
被unsubscribes
时调用。
stores.js
1 2 3 4 5 6 7 8 9 10 11 12
| import { readable } from 'svelte/store';
export const time = readable(new Date(), function start(set) { const interval = setInterval(() => { set(new Date()); }, 1000);
return function stop() { clearInterval(interval); }; });
|
可以通过下面的实例来加深体验和记忆。
REPL
四、派生 Stores
你可以创建一个stores
,其内的值可以派生(derived)
于一个或多个 其他 stores
。在前面的示例的基础上,我们可以创建派生时间到其他页面:来看例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { readable, derived } from 'svelte/store';
export const time = readable(new Date(), function start(set) { const interval = setInterval(() => { set(new Date()); }, 1000);
return function stop() { clearInterval(interval); }; });
const start = new Date();
export const elapsed = derived( time, $time => Math.round(($time - start) / 1000) );
|
可从多个 store 派生为一个 store,并显式地 set 一个值而不是返回它(这对于异步派生的值很有用处)。有关更多信息,请查阅API 参考。
五、自定义状态
只要一个对象正确地实现了 subscribe
方法,它就是一个 store
。除了之外,一切皆有可能。因此,使用特定领域的逻辑来创建自定义store
非常容易。
来看个例子
App
1 2 3 4 5 6 7 8 9
| <script> import { count } from './stores.js'; </script>
<h1>The count is {$count}</h1>
<button on:click={count.increment}>+</button> <button on:click={count.decrement}>-</button> <button on:click={count.reset}>reset</button>
|
store.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { writable } from 'svelte/store';
function createCount() { const { subscribe, set, update } = writable(0);
return { subscribe, increment: () => {}, decrement: () => {}, reset: () => {} }; }
export const count = createCount();
|
例如, 这个 count store
在前面的例子中包含 increment
、 decrement
和 reset
组件,以防止暴露 set
和update
方法,让我们改写一下:
1 2 3 4 5 6 7 8 9 10
| function createCount() { const { subscribe, set, update } = writable(0);
return { subscribe, increment: () => update(n => n + 1), decrement: () => update(n => n - 1), reset: () => set(0) }; }
|
六、状态绑定
如果 store
可写入的(即具有set方法),则可以绑定其值,就像可以绑定局部组件状态一样。
看例子
App
1 2 3 4 5 6
| <script> import { name, greeting } from './stores.js'; </script>
<h1>{$greeting}</h1> <input value={$name}>
|
store.js
1 2 3 4 5 6
| import { writable, derived } from 'svelte/store'; export const name = writable('world'); export const greeting = derived( name, $name => `Hello ${$name}!` );
|
在此示例中,我们有一个可写 store:name
和一个派生store :greeting
,尝试更改<input>
标签:
1
| <input bind:value={$name}>
|
现在,更改name
的输入值 ,其值和依赖项都将获得更新。
我们还可以直接分配store
值在组件内部,尝试添加<button>
标签:
1 2 3
| <button on:click="{() => $name += '!'}"> Add exclamation mark! </button>
|
此处 $name += '!'
相当于 name.set($name + '!')
。
总结
- 可写状态
writable
是我们常用的,只读状态 readable
是在防止被意外修改的情况下使用。
- 要读取状态的值,使用状态的
subscribe
方法,当然你最常用的还是使用自动订阅
的简写形式,使用 $
符号
- 状态派生,可以快速从一个已存在的状态中将其复用。
- 状态同时也支持绑定。