React вирішує, чи зберегти компонент між рендерами, за двома ознаками: позиція в дереві і тип (функція-компонент). Якщо на тій самій позиції опинився інший тип - старий компонент знищується разом зі станом і DOM, новий створюється з нуля.
Типова помилка:
function VacancyPage({ vacancy }) {
const [tab, setTab] = useState('description');
// нова функція на КОЖНОМУ рендері VacancyPage
function ApplyForm() {
const [email, setEmail] = useState('');
return <input value={email} onChange={(e) => setEmail(e.target.value)} />;
}
return (
<>
<Tabs value={tab} onChange={setTab} />
<ApplyForm />
</>
);
}
Кожен рендер VacancyPage створює нову функцію ApplyForm - для React це новий тип компонента. Наслідки:
- стан скидається: введений email зникає при перемиканні вкладки;
- DOM перестворюється - втрачається фокус, позиція курсору, анімації;
- дорожче: монтування з нуля замість оновлення.
Рішення: оголошувати компоненти на верхньому рівні модуля і передавати дані через props. Правило static-components з eslint-plugin-react-hooks ловить цю помилку автоматично.
Та сама механіка свідомо - key для скидання стану:
<ProfileForm key={userId} user={user} />
Зміна key - новий компонент: форма скидається при переході до іншого користувача. Це краще, ніж синхронізувати стан з props в ефекті.
Але key має ціну. Перемонтування - це знищення й створення всього піддерева: ефекти очищення й запуску, нові запити, втрата стану в усіх нащадках. Зміна key на великому дереві без потреби (наприклад, key={Math.random()} чи key={JSON.stringify(filters)} на всій сторінці) - джерело повільності й «миготіння».
Інші випадки втрати стану через позицію:
- умовний рендер, що змінює структуру:
{isEditing ? <Form /> : <Preview />}на тій самій позиції - стан форми зникає при перемиканні. Якщо стан треба зберегти - тримати його вище чи ховати компонент CSS; - різні обгортки:
<div><Counter /></div>проти<section><Counter /></section>- інший тип батька, лічильник скидається; - індекс у
keyсписку - при видаленні першого елемента стан «переїжджає» на сусідні.
Правило: тип і позиція компонента мають бути стабільними, а скидання стану - явним, через key.