11. Сторы: createStore
createStore — примитив для работы с вложенными реактивными объектами. В отличие от сигнала, который хранит одно значение, стор поддерживает глубокую реактивность — обновление вложенного поля не пересоздаёт весь объект.
createStore vs createSignal
Заголовок раздела «createStore vs createSignal»import { createSignal, createStore } from 'solid-js';
// createSignal — для примитивов и плоских объектовconst [count, setCount] = createSignal(0);const [user, setUser] = createSignal({ name: 'Alice', age: 25 });// Обновление всего объекта целиком:setUser(prev => ({ ...prev, age: 26 }));
// createStore — для вложенных объектовconst [state, setState] = createStore({ user: { name: 'Alice', age: 25 }, settings: { theme: 'dark', notifications: true }, todos: [] as Todo[],});
// Обновление конкретного поля — остальное не трогаетсяsetState('user', 'age', 26);// Эквивалент spread: setState('user', prev => ({ ...prev, age: 26 }))Синтаксис обновления (path syntax)
Заголовок раздела «Синтаксис обновления (path syntax)»Solid использует path-синтаксис для обновления стора — строки, числа, функции:
const [store, setStore] = createStore({ todos: [ { id: 1, text: 'Учить Solid', done: false }, { id: 2, text: 'Написать компонент', done: false }, ], user: { name: 'Bob', score: 100 }, config: { theme: 'dark', lang: 'ru' },});
// Простое полеsetStore('user', 'name', 'Alice');
// Вложенное полеsetStore('config', 'theme', 'light');
// По индексу массиваsetStore('todos', 0, 'done', true);
// По функции-фильтру — обновить все подходящие элементыsetStore('todos', todo => !todo.done, 'done', true);
// Через функцию (как в setState React)setStore('user', 'score', prev => prev + 10);
// Замена всего массиваsetStore('todos', [/* новый массив */]);produce — immer-стиль мутаций
Заголовок раздела «produce — immer-стиль мутаций»produce позволяет писать мутирующий код — под капотом он иммутабелен:
import { createStore } from 'solid-js/store';import { produce } from 'solid-js/store';
const [todos, setTodos] = createStore<Todo[]>([]);
// Вместо path-синтаксиса — привычные мутацииconst addTodo = (text: string) => { setTodos(produce(todos => { todos.push({ id: Date.now(), text, done: false }); }));};
const toggleTodo = (id: number) => { setTodos(produce(todos => { const todo = todos.find(t => t.id === id); if (todo) todo.done = !todo.done; }));};
const editTodo = (id: number, text: string) => { setTodos(produce(todos => { const todo = todos.find(t => t.id === id); if (todo) todo.text = text; }));};reconcile — умное слияние с внешними данными
Заголовок раздела «reconcile — умное слияние с внешними данными»reconcile используется когда данные приходят извне (API) и нужно минимально обновить стор:
import { reconcile } from 'solid-js/store';
const [todos, setTodos] = createStore<Todo[]>([]);
// Без reconcile — заменяет весь массив, пересоздаёт все DOM-узлыasync function fetchTodos() { const data = await getTodos(); setTodos(data); // Плохо: все компоненты перерендерятся}
// С reconcile — diff-алгоритм обновляет только изменившеесяasync function fetchTodos() { const data = await getTodos(); setTodos(reconcile(data)); // Хорошо: обновляются только изменившиеся элементы}
// reconcile по ключуsetTodos(reconcile(newData, { key: 'id', merge: false }));Хранение массивов в сторе
Заголовок раздела «Хранение массивов в сторе»interface AppState { users: User[]; selectedId: number | null; loading: boolean;}
const [state, setState] = createStore<AppState>({ users: [], selectedId: null, loading: false,});
// Добавить элементsetState('users', users => [...users, newUser]);
// Удалить элементsetState('users', users => users.filter(u => u.id !== id));
// Обновить конкретный элемент по индексуconst idx = state.users.findIndex(u => u.id === id);if (idx >= 0) setState('users', idx, 'name', 'New Name');
// Обновить по условию — все элементы с полем active: falsesetState('users', u => !u.active, 'active', true);Вычисляемые значения из стора
Заголовок раздела «Вычисляемые значения из стора»const [store, setStore] = createStore({ todos: [] as Todo[], filter: 'all' as 'all' | 'active' | 'done',});
// createMemo работает со стором так же, как с сигналамиconst filteredTodos = createMemo(() => { if (store.filter === 'all') return store.todos; if (store.filter === 'done') return store.todos.filter(t => t.done); return store.todos.filter(t => !t.done);});
const stats = createMemo(() => ({ total: store.todos.length, done: store.todos.filter(t => t.done).length, active: store.todos.filter(t => !t.done).length,}));