Перейти к содержимому

11. Сторы: createStore

createStore — примитив для работы с вложенными реактивными объектами. В отличие от сигнала, который хранит одно значение, стор поддерживает глубокую реактивность — обновление вложенного поля не пересоздаёт весь объект.

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 }))

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 позволяет писать мутирующий код — под капотом он иммутабелен:

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 используется когда данные приходят извне (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: false
setState('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,
}));