我们熟谙 React 的 Redux,或者 Vue 的 Vuex,它们都是以程序包的形式提供,对于复杂的状态管控,还可能用 mobx 或者 rxjs 之类的专业库。
当期望脱离组件的层级关系且能够在任意位置都能访问某个状态时,尤其是有些状态需要被多个毫不相干的组件或普通的 JavaScript 模块访问的时候,状态管理仍然是非常有用的一个特性。
Svelte 将状态划基本分为两种,一种可读可写,一种只读。可写状态 writable 是我们最常用的状态,只读状态 readable 是在防止被意外修改的情况下使用。
我们可以将创建状态的代码,放到单独的文件 store.js 中,以便其他需要用到对应状态的组件可以引入使用并利于统一的管理和维护。
可写状态(Writable stores)
// 可写状态引入的是svelte/store
import { writable } from 'svelte/store';
const count = writable(0); // 此时count就是一个可读写store 0是store所存储的值
在Svelte中, store(也即状态)只不过是具有 subscribe 方法的普通对象,
可以认为只要一个对象正确地实现了 subscribe 方法,它即是一个 store
它允许当 store 的值改变时自动推送(通知)给所有subscribe(订阅,引用)了这个状态的订阅者。
let count_value;
// count 是一个store
// subscribe方法传入一个callback 表示的是count_value自动订阅了count这个状态,即count_value的值时刻和count的值保持一致
// 该callback会在组件初始化以及count的值发生改变的时候自动被调用,以更新count_value的值为最新的count值
count.subscribe(value => {
count_value = value;
});
<script>
import { onDestroy } from 'svelte';
import { count } from './stores.js';
import Incrementer from './Incrementer.svelte';
import Decrementer from './Decrementer.svelte';
import Reset from './Reset.svelte';
let count_value;
// subscribe会返回一个函数,这个函数用于取消订阅
// 以免该组件在多次实例化和销毁的时候造成内存泄露
const unsubscribe = count.subscribe(value => {
count_value = value;
});
onDestroy(unsubscribe);
</script>
<h1>count 当前的值是:{count_value}</h1>
当一个store是一个 writable store(可写状态),这表示除了 subscribe 方法外,它还具有 set 和 update 方法。
// 基于原本的store值对store的值进行更新
count.update(n => n + 1);
// 设置store值为一个新值
count.set(0);
自动订阅(Auto-subscriptions)
count.subscribe写起来比较繁琐,而且需要在onDestory中,手动取消订阅。
如果使用的store比较多的时候,代码会显得呆板重复。
Svelte为我们提供了对应的语法糖, 在 store 名称前面加上$前缀来引用这个 store 的值。
所以Svelte 假定所有以 $ 开头的任何标识符都表示引用某个 store 值,而 $ 实际上是一个保留字符,
Svelte 会禁止你使用 $ 作为你声明的变量的前缀。
<script>
import { count } from './stores.js';
</script>
<h1>count 当前的值是:{$count}</h1>
只读状态(Readable stores)
并非所有 store 允许所有人可写的。
可能有一个 store 存储用户的信息或者存储需要发送给服务器做访问标识的token等,修改这个值是没有意义的。
// readable有2个参数
// 参数1: store的初始值,如果没有可以先设置为null或undefined
// 参数2: 一个callback函数 该callback接受一个 set 回调用于设置值,并且返回一个 stop 函数。
// 当 store 被第一个订阅者读取时,就会调用 start 函数
// 当 最后一个订阅者退订时,调用 stop 函数。
// 当然 如果这个store不需要在内部被更新的时候,第二个参数是可以省略的
export const time = readable(new Date(), function start(set) {
// 每过1s,将time修改为当前时间,也就是说只读store并不是不能被改变
// 只不过如果要修改只读store的值,只能通过预先定义好的callback
// 外部订阅了store值的变量无法从'外部'修改只读store的值
const interval = setInterval(() => set(new Date()), 1000);
return function stop() { clearInterval(interval); };
});
状态继承(Derived stores)
可以调用derived来创建一个新的 store,它将继承自某一个或多个其他的 store
<script>
import {writable, derived} from 'svelte/store';
// 定义一个可修改的store
let num = writable(0)
// double是派生自num,当num发生改变的时候会自动根据预先设定的规则获取新的值
// 派送处理的store是一个readable store
let double = derived(
num, // 传入的是store对象,所以是num不是$num
$num => $num * 2
)
setInterval(()=> $num+=1, 1000)
</script>
<h1>num = {$num}</h1>
<h1>double = {$double}</h1>
<script>
import {writable, derived} from 'svelte/store';
let num1 = writable(0)
let num2 = writable(2)
// 可从多个 store 派生为一个 store
let sum = derived(
[num1, num2], // 多个store以数组的形式进行传入
$nums => $nums[0] + $nums[1]
)
setInterval(()=> {
$num1 += 1
$num2 *= 2
}, 1000)
</script>
<h1>num1 = {$num1}</h1>
<h1>num2 = {$num2}</h1>
<h1>sum = {$sum}</h1>
自定义状态(Custom stores)
在日常开发过程中,我们通常将状态写到单独的 JS 文件中,例如每个状态一份 JS 文件
将和某个状态有关的操作封装在一起,然后统一导出,以避免暴露 set 和 update 方法和对应的更新逻辑
自定义状态使我们有机会封装一个状态,使其更贴近业务,使用起来也更为便捷
function createCount() {
const { subscribe, set, update } = writable(0);
return {
subscribe,
increment: () => update(n => n + 1),
decrement: () => update(n => n - 1),
reset: () => set(0)
};
}
状态绑定(Store bindings)
在Svelte中,状态在模板中的绑定和使用,与一般的数据没有较大的区别
<input bind:value={$name}>
<button on:click="{() => $name += '!'}">
Add exclamation mark!
</button>