Как useState сохраняет предыдущее состояние?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как useState сохраняет состояние в React
useState - это хук React, который позволяет функциональным компонентам иметь локальное состояние. Механизм его работы включает несколько интересных деталей.
Концепция: Closure и Fiber Architecture
React сохраняет состояние не в самом компоненте, а в специальной структуре данных, связанной с компонентом. Это возможно благодаря двум механизмам:
1. Замыкание (Closure)
Каждый компонент имеет область видимости, в которой создаются переменные состояния:
function Counter() {
// useState создаёт переменную, которая "запоминается"
const [count, setCount] = React.useState(0);
// count и setCount доступны благодаря замыканию
// функция setCount может изменить count через замыкание
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
2. Fiber Architecture
React использует внутреннюю структуру "fiber" для каждого компонента:
// Упрощённое внутреннее представление (не реальный код React)
const ComponentFiber = {
component: Counter,
state: [
{ value: 0, queue: [] }, // Первый useState
{ value: 'John', queue: [] } // Второй useState
],
hooks: [
{ memoizedState: 0, baseState: 0, ... },
{ memoizedState: 'John', baseState: 'John', ... }
]
};
Как это работает в деталях
Инициализация
При первом рендере компонента:
function UserProfile() {
// При первом вызове: initialValue = 0
const [count, setCount] = React.useState(0);
// React создаёт fiber и сохраняет { value: 0 }
}
// React регистрирует это в своей памяти:
// fiber.state[0] = { value: 0, queue: [] }
Хранение значения
Когда вы вызываете useState, React возвращает текущее значение из памяти:
function Counter() {
// React смотрит в fiber.state[0] и находит { value: 5 }
// Возвращает: [5, setter]
const [count, setCount] = React.useState(0);
// count = 5 (из памяти React, не из параметра)
console.log(count); // 5, а не 0!
}
Обновление состояния
Когда вы вызываете setCount:
const [count, setCount] = React.useState(0);
setCount(5);
// Что происходит внутри:
// 1. React добавляет обновление в очередь:
// fiber.state[0].queue.push({ action: 5 })
// 2. React планирует новый render
// 3. При новом render React берёт новое значение из памяти
// 4. count теперь = 5 вместо 0
Идентификация setState с помощью порядка вызовов
React использует порядок вызовов useState для отслеживания, какое состояние какому относится:
function Profile() {
const [name, setName] = useState('John'); // useState #1
const [age, setAge] = useState(30); // useState #2
const [email, setEmail] = useState('j@e.com'); // useState #3
// React запоминает:
// hooks[0] = name
// hooks[1] = age
// hooks[2] = email
}
ВАЖНО: Порядок должен быть консистентным!
// ПЛОХО: нарушает порядок
function BadExample({ condition }) {
if (condition) {
const [name, setName] = useState('John'); // Может быть #1 или #2
}
const [age, setAge] = useState(30); // Может быть #2 или #1
}
// ХОРОШО: всегда одинаковый порядок
function GoodExample({ condition }) {
const [name, setName] = useState('John');
const [age, setAge] = useState(30);
if (condition) {
// Используем значения здесь
}
}
Под капотом: как React хранит значения
1. React элемент и его fiber
const element = <Counter />;
// Каждому элементу соответствует fiber
// Fiber содержит:
// - Ссылку на функцию компонента
// - memoizedState (текущее состояние)
// - update queue (очередь обновлений)
2. Цепочка hooks (linked list)
React связывает все useState в список:
// Упрощённое представление
functionalComponent.memoizedState = {
state: 'John', // Значение первого useState
queue: [],
next: {
state: 30, // Значение второго useState
queue: [],
next: null
}
};
3. Update queue (очередь обновлений)
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1); // #1
setCount(count + 1); // #2
setCount(count + 1); // #3
};
// Все три вызова добавляются в queue:
// queue = [
// { action: count + 1 },
// { action: count + 1 },
// { action: count + 1 }
// ]
//
// При обработке React применяет их последовательно
}
Сохранение между рендерами
Каждый компонент получает свой fiber:
function App() {
return (
<>
<Counter /> {/* Fiber #1 с собственным state */}
<Counter /> {/* Fiber #2 с собственным state */}
</>
);
}
// Две разные переменные count в разных компонентах
// каждая сохраняется в своём fiber
Сохранение при условном рендере:
function ConditionalComponent() {
const [count, setCount] = useState(0);
const [isVisible, setIsVisible] = useState(true);
if (!isVisible) {
return <div>Hidden</div>;
}
// Даже если компонент вернул другой JSX,
// React помнит count и другие состояния в fiber
return <div>{count}</div>;
}
Практический пример
function FormComponent() {
// Инициально React запоминает эти значения
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [email, setEmail] = useState('');
// При каждом рендере React берёт значения из памяти
// не из параметров useState
return (
<>
<input
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
/>
<input
value={lastName}
onChange={(e) => setLastName(e.target.value)}
/>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<p>Data: {firstName}, {lastName}, {email}</p>
</>
);
}
При вводе текста:
1. onChange -> setFirstName('J')
2. React обновляет fiber.state[0].value = 'J'
3. React перерендеривает компонент
4. useState('') возвращает 'J' из памяти
5. input.value = 'J' отображается
Почему это работает
- Fiber Architecture - React хранит состояние привязано к каждому экземпляру компонента
- Замыкание - setCount имеет доступ к переменным через closure
- Консистентный порядок - React полагается на порядок вызовов
- Связанный список - все useState в компоненте связаны в цепь
Этот механизм позволяет React автоматически сохранять состояние между рендерами и обновлять компонент при изменении этого состояния.