Паттерны состояния
Несколько проверенных приёмов организации состояния.
Один источник истины
Состояние хранится в одном месте. Производные данные вычисляются, а не дублируются.
// Плохо
const [items, setItems] = useState<Item[]>([]);
const [total, setTotal] = useState(0);
// Хорошо
const [items, setItems] = useState<Item[]>([]);
const total = items.reduce((sum, i) => sum + i.price, 0);
Производное состояние
Не храни в стейте то, что можно вычислить.
// Плохо
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState('');
// Хорошо
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const fullName = `${firstName} ${lastName}`;
Нормализация
Если данные вложенные — храни их плоско.
// Плохо
const [posts, setPosts] = useState<Post[]>([]);
// post.author.name — дублируется в каждом посте
// Хорошо
const [posts, setPosts] = useState<Record<number, Post>>({});
const [users, setUsers] = useState<Record<number, User>>({});
// post.authorId — ссылка
Разделение состояния
Разделяй независимые части. Не пихай всё в один объект.
// Плохо
const [state, setState] = useState({
user: null,
theme: 'light',
sidebarOpen: false,
notifications: [],
});
// Хорошо
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const [sidebarOpen, setSidebarOpen] = useState(false);
Флаги vs статусы
Вместо нескольких булевых флагов — один статус.
// Плохо
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
// Хорошо
const [status, setStatus] = useState<'idle' | 'loading' | 'error' | 'success'>('idle');
Невозможные состояния становятся невозможными.
Группировка связанных обновлений
Если несколько значений меняются вместе — объединяй.
const [form, setForm] = useState({ name: '', email: '' });
Reducer для сложных переходов
Если логика обновления сложная — useReducer.
const [state, dispatch] = useReducer(reducer, initialState);
Локальный vs глобальный
Держи состояние как можно ближе к использованию:
- В компоненте — если нужно только ему
- В родителе — если нужно соседям
- В контексте — если глубоко в дереве
- В сторе — если глобально
URL как состояние
Фильтры, сортировка, пагинация — лучше в URL:
const [searchParams, setSearchParams] = useSearchParams();
const page = Number(searchParams.get('page') ?? 1);
Плюсы: можно поделиться ссылкой, работают кнопки назад/вперёд.
Форма как состояние
Для форм — библиотека React Hook Form. Меньше ререндеров, встроенная валидация.
Серверное состояние
Не храни серверные данные в своём стейте. TanStack Query или SWR.
Итоги
- Один источник истины
- Производное не храни
- Нормализуй вложенное
- Разделяй независимое
- Статус вместо флагов
- URL — для фильтров
- Серверное — в Query