Состояние, производные значения и reducer
Правильный useState, иммутабельные обновления, производное состояние и useReducer.
Обновлено
Что такое состояние
Состояние (state) = память компонента: данные, которые меняются со временем из-за взаимодействия (поле ввода, переключатель, счётчик). Когда состояние меняется, React перерисовывает компонент с новым значением.
Почему не обычная переменная? Локальная переменная (let count = 0) сбрасывается при каждом рендере, и её изменение не вызывает обновления экрана.
const [count, setCount] = useState(0)countтекущее значениеsetCountфункция, которая его меняет и запрашивает перерисовку0начальное значение
Props и state
| Props | State | |
|---|---|---|
| Приходит от | родителя | самого компонента |
| Можно менять внутри компонента | нет | да, через сеттер |
| Роль | настройка снаружи | внутренняя память |
Как работает сеттер
setCount(count + 1) // новое значение
setCount(c => c + 1) // функция-обновитель — получает самое свежее значение- Сеттер не меняет переменную сразу; он планирует новый рендер.
console.log(count)сразу после покажет старое значение. - React группирует несколько обновлений (batching) в один рендер.
- Когда новое значение зависит от старого → функция-обновитель:
setCount(c => c + 1)три раза даёт +3;setCount(count + 1)три раза даёт +1.
Состояние иммутабельно
React сравнивает старое и новое состояние по ссылке. Объекты и массивы заменяют, а не изменяют — см. Ссылки и иммутабельность.
setUser({ ...user, name }) // ✓
setTodos(todos => [...todos, newTodo]) // ✓
user.name = name; setUser(user) // ✗ та же ссылка → перерисовки нетПроизводное состояние — не храните его
Если значение можно вычислить из props или состояния, вычисляйте его при рендере. Не держите его в отдельном useState.
// ✗ два источника истины + эффект, который их синхронизирует
const [todos, setTodos] = useState([])
const [doneCount, setDoneCount] = useState(0)
// ✓ один источник, остальное выводится
const doneCount = todos.filter(t => t.done).lengthДействительно дорогие вычисления → useMemo (или React Compiler, который мемоизирует автоматически).
Где хранить состояние — варианты и когда
| Где | Когда | Пример |
|---|---|---|
локальный useState |
нужен одному компоненту | поле ввода, открытый выпадающий список |
| поднятое в родителя (lift state up) | нужно двум соседям | фильтр + список |
URL (?page=2) |
нужно делиться ссылкой и пережить перезагрузку | фильтры, пагинация, активная вкладка |
| Context | много глубоких компонентов, редкие изменения | тема, вошедший пользователь, язык |
| стор (Zustand) | глобальное, меняется часто | корзина в SPA |
| кэш серверного состояния | данные с сервера | Серверное состояние |
Правило: как можно ниже, поднимаете только при необходимости.
useReducer — когда логика растёт
Когда у состояния много способов измениться, логику собирают в reducer — чистую функцию (state, action) => newState:
function todosReducer(state, action) {
switch (action.type) {
case 'add': return [...state, action.todo]
case 'toggle': return state.map(t => (t.id === action.id ? { ...t, done: !t.done } : t))
case 'remove': return state.filter(t => t.id !== action.id)
default: return state
}
}
const [todos, dispatch] = useReducer(todosReducer, [])
dispatch({ type: 'add', todo })useState |
useReducer |
|---|---|
| 1–3 простых значения | состояние с множеством переходов |
| логика в обработчиках | логика в одном месте, тестируется отдельно |
Коротко
- Состояние = память компонента; сеттер запрашивает перерисовку.
- Функция-обновитель, когда зависите от старого значения; всегда новые объекты.
- Выводите то, что можно вычислить; держите состояние как можно ниже; URL — для того, чем нужно делиться.