Хранилища
Хранилище — это объект, который позволяет реактивно получать доступ к значению через простой контракт хранилища. Модуль svelte/store
содержит минимальные реализации хранилищ, которые соответствуют этому контракту.
Всякий раз, когда у вас есть ссылка на хранилище, вы можете получить доступ к его значению внутри компонента, добавив перед ним символ $
. Это заставляет Svelte объявить переменную с префиксом, подписаться на хранилище при инициализации компонента и отписаться в нужный момент.
Присвоения переменным с префиксом $
требуют, чтобы переменная была записываемым хранилищем (writable store
), и приведут к вызову метода .set
хранилища.
Обратите внимание, что хранилище должно быть объявлено на верхнем уровне компонента — не внутри блока if
или функции, например.
Локальные переменные (которые не представляют значения хранилища) не должны иметь префикс $
.
<script>
import { writable } from 'svelte/store';
const count = writable(0);
console.log($count); // выведет 0
count.set(1);
console.log($count); // выведет 1
$count = 2;
console.log($count); // выведет 2
</script>
Когда использовать хранилища
До Svelte 5 хранилища были основным решением для создания реактивных состояний, общих для нескольких компонентов, или для вынесения логики. С появлением рун эти случаи применения значительно сократились.
- при вынесении логики лучше использовать универсальную реактивность рун: вы можете использовать руны вне верхнего уровня компонентов и даже размещать их в JavaScript или TypeScript файлах (используя расширение
.svelte.js
или.svelte.ts
); - при создании общего состояния вы можете создать объект
$state
, содержащий необходимые значения, а затем управлять этим состоянием.
// state.svelte.js
export const userState = $state({
name: 'name',
/* ... */
});
// App.svelte
<script>
import { userState } from './state.svelte.js';
</script>
<p>Имя пользователя: {userState.name}</p>
<button onclick={() => {
userState.name = 'новое имя';
}}>
изменить имя
</button>
// App.svelte
<script lang="ts">
import { userState } from './state.svelte.js';
</script>
<p>Имя пользователя: {userState.name}</p>
<button onclick={() => {
userState.name = 'новое имя';
}}>
изменить имя
</button>
Хранилища по-прежнему являются хорошим решением, если у вас сложные асинхронные потоки данных или важно иметь более ручное управление обновлением значений или отслеживанием изменений. Если вы знакомы с RxJs и хотите повторно использовать эти знания, символ $
также вам пригодится.
svelte/store
Модуль svelte/store
содержит минимальную реализацию хранилища, которая соответствует контракту хранилища. Он предоставляет методы для создания хранилищ, которые можно обновлять извне, хранилищ, которые можно обновлять только изнутри, а также для комбинирования и создания производных хранилищ.
writable
Функция, которая создает хранилище, значения которого можно задавать «извне» компонентов. Оно создается как объект с дополнительными методами set
и update
.
set
— это метод, принимающий один аргумент — значение для установки в хранилище. Значение обновляется, если оно отличается от текущего значения хранилища.update
— это метод, который принимает один аргумент — колбэк-функцию. Колбэк принимает текущее значение хранилища в качестве аргумента и возвращает новое значение, которое будет установлено в хранилище.
// store.js
import { writable } from 'svelte/store';
const count = writable(0);
count.subscribe((value) => {
console.log(value);
}); // выведет '0'
count.set(1); // выведет '1'
count.update((n) => n + 1); // выведет '2'
Если функция передается в качестве второго аргумента, она будет вызвана, когда количество подписчиков изменится с нуля на один (но не с одного на два и т. д.). Этой функции будут переданы:
- функция
set
, которая изменяет значение хранилища, - и функция
update
, работающая аналогично методуupdate
хранилища, принимающая колбэк для вычисления нового значения хранилища на основе старого.
Функция должна вернуть метод stop
, который будет вызван, когда количество подписчиков изменится с одного на ноль.
// store.js
import { writable } from 'svelte/store';
const count = writable(0, () => {
console.log('получил подписчика');
return () => console.log('подписчиков больше нет');
});
count.set(1); // ничего не произойдёт
const unsubscribe = count.subscribe((value) => {
console.log(value);
}); // выведет 'получил подписчика', затем '1'
unsubscribe(); // выведет 'подписчиков больше нет'
Обратите внимание, что значение writable
хранилища теряется при его уничтожении, например, при обновлении страницы. Однако вы можете реализовать собственную логику для синхронизации значения, например, с localStorage
.
readable
Создает хранилище, значение которого нельзя задать «извне». Первый аргумент — начальное значение хранилища, а второй аргумент для readable
совпадает со вторым аргументом для writable
.
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('тик', (set, update) => {
const interval = setInterval(() => {
update((sound) => (sound === 'тик' ? 'ток' : 'тик'));
}, 1000);
return () => clearInterval(interval);
});
derived
Создает производное хранилище на основе одного или нескольких других хранилищ. Колбэк выполняется при первой подписке, а затем каждый раз, когда изменяются зависимости хранилища.
В простейшем случае derived
принимает одно хранилище, а колбэк возвращает производное значение.
import { derived } from 'svelte/store';
const doubled = derived(a, ($a) => $a * 2);
Колбэк может задавать значение асинхронно, принимая второй аргумент — set
, и, опционально, третий аргумент — update
, вызывая их при необходимости.
В этом случае вы также можете передать третий аргумент в derived
— начальное значение производного хранилища до первого вызова set
или update
. Если начальное значение не указано, им будет 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);
// Каждый раз, когда $a выдает значение, это порождает два значения:
// $a сразу и затем $a + 1 через секунду
});
Если вы вернете функцию из колбэка, она будет вызвана в двух случаях:
- когда колбэк выполнится снова, или
- когда последний подписчик отпишется.
import { derived } from 'svelte/store';
const tick = derived(
frequency,
($frequency, set) => {
const interval = setInterval(() => {
set(Date.now());
}, 1000 / $frequency);
return () => {
clearInterval(interval);
};
},
2000
);
В обоих случаях массив аргументов может быть передан в качестве первого аргумента вместо одного хранилища.
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);
});
readonly
Эта простая вспомогательная функция делает хранилище доступным только для чтения. Вы по-прежнему можете подписываться на изменения исходного хранилища, используя это новое хранилище.
import { readonly, writable } from 'svelte/store';
const writableStore = writable(1);
const readableStore = readonly(writableStore);
readableStore.subscribe(console.log);
writableStore.set(2); // консоль: 2
readableStore.set(2); // ОШИБКА
get
Обычно рекомендуется читать значение хранилища, подписавшись на него и используя значение по мере его изменения. Однако иногда может потребоваться получить значение хранилища, на которое вы не подписаны. get
позволяет это сделать.
import { get } from 'svelte/store';
const value = get(store);
Контракт хранилища
store = { subscribe: (subscription: (value: any) => void) => (() => void), set?: (value: any) => void }
Вы можете создавать собственные хранилища, не полагаясь на svelte/store
, реализовав контракт хранилища:
- Хранилище должно содержать метод
.subscribe
, который принимает в качестве аргумента функцию подписки. Эта функция подписки должна быть немедленно и синхронно вызвана с текущим значением хранилища при вызове.subscribe
. Все активные функции подписки хранилища должны синхронно вызываться при каждом изменении значения хранилища. - Метод
.subscribe
должен возвращать функцию отписки. Вызов функции отписки должен прекратить подписку, и соответствующая функция подписки больше не должна вызываться хранилищем. - Хранилище может опционально содержать метод
.set
, который принимает новое значение для хранилища в качестве аргумента и синхронно вызывает все активные функции подписки. Такое хранилище называется записываемым хранилищем.
Для совместимости с RxJS Observables метод .subscribe
также может возвращать объект с методом .unsubscribe
, вместо того чтобы возвращать функцию отписки напрямую. Однако обратите внимание, что если .subscribe
не вызывает функцию подписки синхронно (что не требуется спецификацией Observable), Svelte будет видеть значение хранилища как undefined
, пока это не произойдет.