Стан React сьогодні: від класів до хуків і далі

Останнє оновлення: 04/22/2026
Автор: C SourceTrail
  • Стан React має розглядатися як незмінний, а оновлення виконуються через сетери, а не безпосередньо через мутації, особливо для об'єктів та масивів.
  • Оновлення стану є асинхронними та можуть бути пакетними, тому використання функціональних оновлень дозволяє уникнути проблем із застарілим станом у таймерах, закриттях та швидких взаємодіях.
  • Функціональні компоненти з хуками (useState, useRef та подібні) є сучасним стандартом, тоді як інструменти, такі як React.memo та Immer, допомагають з продуктивністю та вкладеними даними.
  • Чітке розділення властивостей та стану, а також низхідна модель потоку даних забезпечують передбачуваність поведінки компонентів у міру масштабування застосунків.

Концепція управління станом реагування

State — це одна з тих концепцій React, яка на перший погляд виглядає простою, але швидко стає складною в міру зростання вашого застосунку. Ви починаєте з невеликого лічильника, а потім раптом жонглюєте кількома полями форми, асинхронними оновленнями, вкладеними об'єктами та проблемами продуктивності, коли все перерендерується одночасно. Глибоке розуміння стану - це те, що відрізняє того, хто «користується React», від того, хто може масштабувати та налагоджувати реальні React-додатки.

У цьому посібнику ми розглянемо поточний стан React (каламбур навмисний), від компонентів класу та методів життєвого циклу до сучасних хуків та незмінних оновлень. Ми також розглянемо тонкі, але важливі теми, такі як асинхронні оновлення, застарілі замикання, коли використовувати useRef замість useState та як зробити ваш інтерфейс користувача передбачуваним. Мета полягає в тому, щоб надати вам чітку ментальну модель, щоб ваші компоненти поводилися саме так, як ви очікуєте.

Від реквізиту до стану: що насправді куди належить?

Пропи та стан у React

В основі кожного React-компонента є два основних джерела даних: props та state. бутафорія передаються з батьківського компонента та залишаються фіксованими протягом усього терміну служби цього рендерингу, тоді як були належить і контролюється самим компонентом і призначений для даних, які змінюються з часом.

Гарне емпіричне правило таке: якщо дані налаштовуються ззовні та не змінюються в цьому компоненті, це проп; якщо компонент повинен відстежувати та оновлювати їх, це стан. Уявіть собі миготливий текстовий компонент: фактичний текст надається один раз (проп), але те, чи він наразі відображається, чи прихований, постійно змінюється (стан). Ця відмінність дозволяє React підтримувати потік даних передбачуваним та односпрямованим.

React заохочує низхідний (однонаправлений) потік даних, де стан знаходиться в найближчому спільному предку, який повинен його контролювати. Батьківський компонент може зберігати стан та передавати значення як props дочірнім компонентам, які можуть їх рендерити або трансформувати, але не повинні знати, чи ці значення спочатку походять зі стану, інших props, чи були жорстко закодовані.

Ось чому ви часто чуєте, що стан є «локальним» або «інкапсульованим». Тільки компонент, якому належить частина стану, може його змінити, і будь-який інтерфейс користувача, похідний від цього стану, проходить вниз через властивості. Ви можете вільно комбінувати компоненти зі станом та без нього (чисті) компоненти, і те, чи є щось зі станом, вважається деталлю реалізації, яка може змінюватися з часом.

Компоненти класу: стан та життєвий цикл по-старому

До появи хуків єдиним способом використання методів стану та життєвого циклу в React були компоненти класу ES6. Хоча більшість сучасних програм спираються на функціональні компоненти, ви все ще побачите (а іноді й підтримуватимете) компоненти класів у багатьох кодових базах, тому варто зрозуміти, як вони працюють.

Щоб перетворити функціональний компонент, наприклад, простий Clock до класу, ви виконуєте кілька механічних кроків. Ви створюєте клас, який розширює React.Component, додайте а render() метод, перемістіть тіло функції в render, замінити props з this.propsта видаліть оригінальну функцію. Поки React продовжує рендеринг <Clock /> в той самий вузол DOM, він повторно використовує один екземпляр цього класу.

Додавання локального стану до класу означає визначення конструктора та призначення початкового значення this.state об'єкт Наприклад, ви можете перемістити date значення з props у стан, додавши конструктор, який викликає super(props) і набори this.state = { date: new Date() }, а потім замінити будь-яке використання this.props.date in render() з this.state.dateПам’ятайте, що в компонентах класу слід призначати значення безпосередньо лише this.state всередині конструктора.

Методи життєвого циклу – це спеціальні методи класу, які React викликає в певні моменти життя компонента. Коли компонент вперше вставляється в DOM (монтується), React викликає componentDidMount()Коли його видалено (розмонтовано), React викликає componentWillUnmount()У класичному прикладі з цокаючим годинником ви встановлюєте таймер у componentDidMount і очистити це componentWillUnmount, зберігаючи ідентифікатор таймера на this (наприклад this.timerId), і виклик this.setState() щосекунди для оновлення часу.

Типовий життєвий цикл такого годинника виглядає так: React викликає конструктор для ініціалізації стану, а потім render() щоб створити DOM, тоді componentDidMount() де ви запускаєте таймер. Щоразу, коли таймер спрацьовує, ви викликаєте setState(), який ставить оновлення в чергу та запускає render() з новим станом. Після видалення компонента, componentWillUnmount() очищує таймер, щоб уникнути втрат ресурсів.

Правильне управління станом у класах також означає дотримання трьох важливих правил щодо setState. Ви не повинні мутувати this.state безпосередньо, вам потрібно пам'ятати, що оновлення можуть бути асинхронними та пакетними, і ви повинні розуміти, що оновлення поверхнево об'єднуються (об'єднуються лише ключі стану верхнього рівня, а не глибоко вкладені об'єкти).

Правильне використання стану: мутації, асинхронні оновлення та потік даних

Одна з найбільших причин плутанини для початківців полягає в тому, що setState (і еквівалент Hook) не оновлює стан негайно, і вам ніколи не слід змінювати об'єкти стану на місці. React часто об'єднує кілька оновлень разом для підвищення продуктивності, тому обидва this.state у класах та змінних стану в хуках можуть не відображати кінцевий стан одразу після планування оновлення.

Безпосередньо мутуючий стан, як-от this.state.count++ або зміна властивостей об'єкта стану пропускає виявлення змін React і може призвести до того, що компоненти залишатимуться застряглими на старих значеннях. React очікує, що ви будете розглядати будь-який об'єкт у стані "лише для читання". Замість того, щоб змінювати існуючі об'єкти, ви створюєте новий об'єкт або масив із потрібними змінами та передаєте його до засобу оновлення стану.

Оскільки оновлення стану можуть бути асинхронними, слід бути обережним під час обчислення наступного стану на основі попереднього. На заняттях щось на кшталт this.setState({ count: this.state.count + 1 }) може бути помилковим, якщо пакетно об'єднано кілька оновлень. Виправлення полягає у використанні функціональної форми: this.setState((prevState, props) => ({ count: prevState.count + 1 }))Це гарантує, що ви працюєте з найновішою версією стану.

Такий самий шаблон існує і з хуками: ви можете викликати засіб оновлення з функцією замість значення. Наприклад, setCount(prev => prev + 1) — це безпечніший спосіб збільшення лічильника, якщо нове значення залежить від попереднього або якщо оновлення можуть відбуватися всередині таймерів чи обробників подій, які запускаються пізніше.

Навіть якщо стан є «локальним», ефект зміни стану завжди поширюється вниз по дереву компонентів. Батьківський повторний рендеринг, ініційований оновленням стану, також за замовчуванням повторно відобразить усі свої дочірні об'єкти. Цей низхідний потік даних є фундаментальним для ментальної моделі React: одне джерело істини зверху, інтерфейс користувача походить з нього знизу.

Сучасний React: хуки та функціональні компоненти

Починаючи з React 16.8, хуки стали стандартним способом керування станом та побічними ефектами у функціональних компонентах. Вони дозволяють використовувати ті ж можливості, що й компоненти класів (і навіть більше), без написання класів чи роботи з... this і методи життєвого циклу явно, apoyándose en el estado estable de JavaScript moderno.

Компоненти функцій тепер є стилем за замовчуванням у кодових базах React. Замість того, щоб писати class Example extends React.Component, ви визначаєте просту функцію, подібну до function Example() { return <div />; }Коли вам потрібен стан, побічні ефекти або посилання, ви «підключаєтеся» до React через такі функції, як useState, useEffect та useRefХуки не можна використовувати всередині класів і вони повинні відповідати Правилам хуків (завжди викликайте їх на верхньому рівні вашого компонента, ніколи в циклах або умовах).

Команда useState Хук – це найпростіший спосіб додати локальний стан до функціонального компонента. Він приймає початкове значення як аргумент і повертає пару: поточне значення стану та сеттер. Завдяки деструктуризації масивів зазвичай ви пишете щось на кшталт const = useState(0)React зберігає цей стан між повторними рендерингами, що означає, що функцію можна викликати багато разів, але значення стану запам'ятовується.

На відміну від стану класу, значення, яке ви зберігаєте в useState не обов'язково має бути об'єктом. Ви можете зберігати числа, рядки, логічні значення, масиви або об'єкти — все, що підходить для даних. Якщо вам потрібно кілька незалежних значень, ви можете викликати useState кілька разів (наприклад, age, fruit, todos). Або ж ви можете зберігати один об'єкт і керувати кількома властивостями всередині нього, але під час оновлення необхідно дотримуватися правил незмінності.

Коли ви викликаєте функцію сеттера, що повертається функцією useState, ви не синхронно змінюєте значення; ви ставите оновлення в чергу, як і з setState у класах. Під час наступного рендерингу React надає вашому компоненту нове значення стану. Саме тому зчитування стану одразу після виклику сеттера всередині тієї ж синхронної функції все одно поверне вам старе значення.

Керування об'єктами та вкладеними даними у стані

React дозволяє передавати будь-яке значення JavaScript у стан, включаючи об'єкти та масиви, але ви повинні розглядати їх як незмінні знімки (snapshots). Примітивні значення, такі як числа та рядки, все одно не можна мутувати, але об'єкти та масиви технічно можна — проте їх мутація порушує припущення React і може призвести до незначних помилок, коли компоненти не оновлюються.

Розглянемо об'єкт стану, такий як { x: 0, y: 0 } що представляє позицію вказівника. Якщо ви пишете position.x = event.clientX безпосередньо, ви змінили існуючий об'єкт. React не має уявлення про зміну значення, оскільки ви ніколи не викликали метод встановлення, тому він не відтворить повторно, і ваш інтерфейс користувача залишиться застряглим. Правильний підхід такий: setPosition({ x: event.clientX, y: event.clientY }), який створює абсолютно новий об'єкт і повідомляє React, що потрібно відрендерити його.

Локальна мутація щойно створених об'єктів цілком нормальна. Наприклад, ви можете створити новий об'єкт крок за кроком: const next = { ...prev }; next.city = 'Paris'; поки next ще не було в стані. Мутація стає проблемою лише тоді, коли ви змінюєте об'єкт, який вже використовується в якомусь попередньому знімку стану, оскільки інші частини вашої програми можуть все ще покладатися на це старе значення.

Щоб оновити лише частину об'єкта, зберігаючи решту, зазвичай використовується синтаксис поширення об'єкта. Для об'єкта стану форми, такого як { firstName, lastName, email }, ви можете обробляти зміни вхідних даних за допомогою чогось на кшталт setPerson({ ...person, : event.target.value })Це копіює старі властивості, а потім перезаписує лише ту, що змінилася. Розкид є неглибоким, тому вкладені об'єкти потребують більшої обережності.

Глибоко вкладені об'єкти можуть швидко призвести до багатослівного коду оновлення, оскільки вам потрібно створювати нові копії на кожному рівні шляху, який ви змінюєте. Наприклад, якщо person.artwork.city зміни, ви б зробили setPerson({ ...person, artwork: { ...person.artwork, city: 'London' } })Під капотом немає «вкладеного об’єкта»; є окремі об’єкти, що вказують один на одного, тому, якщо кілька батьківських об’єктів вказують на один і той самий дочірній об’єкт, і ви його змінюєте, ви змінюєте дані в кількох місцях одночасно.

Якщо ви постійно пишете вкладені розвороти, ви можете розглянути можливість вирівнювання форми стану або використання допоміжної бібліотеки, такої як Immer. Immer дозволяє писати код, який виглядає змінним (наприклад, draft.artwork.city = 'London'), поки він створює для вас нову незмінну копію за лаштунками. У React ви можете поєднати Immer з хуками через useImmer від use-immer пакет.

Стан на практиці: форми, таймери та введення користувачем

У реальних додатках ви рідко керуєте станом лише для лічильників; ви керуєте вводом користувача, відповідями API та «режимами» інтерфейсу користувача, такими як завантаження, помилка та успіх. Ключова зміна мислення в React полягає в тому, що ви не «маніпулюєте DOM» (наприклад, «вимикаєте цю кнопку»); натомість ви описуєте, як має виглядати інтерфейс користувача для кожного стану, а потім оновлюєте цей стан.

Наприклад, компонент вікторини або форми може відстежувати status стан, що перемикається між 'typing', 'submitting' та 'success'. JSX умовно вимикає кнопку надсилання під час надсилання та показує повідомлення про успішне виконання, якщо відповідь правильна. Ви ніколи не викликаєте імперативні методи DOM — React просто повторно рендерить з новим станом, і візуальний вивід змінюється.

Обробка полів форми – це те, де багато розробників вперше стикаються з різницею між об'єднанням станів класів та useState поведінка. У класі, setState об'єднує переданий вами об'єкт з існуючим об'єктом стану, тому оновлення одного поля не видаляє інші. За допомогою useState, оновлення замінюють усе значення: якщо ваш стан є об'єктом, і ви викликаєте setState({ email: '...' }), будь-які інші властивості (наприклад password) зникнуть, якщо ви не об’єднаєте їх вручну.

Ця різниця ставить людей у ​​глухий кут, коли вони рефакторують з кількох примітивних змінних стану в один об'єкт. Якщо ви змінитеся з const та const до const а потім написати загальний setForm({ : value }), ви отримаєте об'єкт стану, який завжди матиме лише одне поле. Виправлення полягає в тому, щоб розширити попередній об'єкт: setForm({ ...form, : value }).

У складніших додатках ви часто не будете викликати setState (Або setSomething) безпосередньо звідусіль. Ви можете централізувати стан за допомогою бібліотек, таких як Redux або MobX, або використовувати useReducer Перехоплення для кінцевих автоматів на рівні компонентів. У цих налаштуваннях ви все ще застосовуєте ті самі принципи незмінності; єдина відмінність полягає в тому, де і як виконуються оновлення.

Повторні рендери, продуктивність та коли використовувати useRef

Кожне оновлення стану в React запускає повторний рендеринг компонента, який володіє станом, та, за замовчуванням, усіх його дочірніх об'єктів. Це задумано задумом: повторний рендеринг — це спосіб синхронізації вашого інтерфейсу користувача з поточними даними. Але це також означає, що бездумне розміщення станів може спричинити непотрібну роботу та повільну роботу інтерфейсів, особливо коли дочірні компоненти виконують ресурсоємні обчислення або відображають великі списки.

Уявіть собі застосунок із полем введення та окремим компонентом, який показує довгий список навичок. Якщо батьківський компонент володіє як текстом, який вводить користувач, так і самим списком, то кожне натискання клавіші повторно відображатиме все дерево, включаючи список навичок, навіть якщо цей список не змінився. Це марна трата зусиль.

Один простий спосіб оптимізувати це — обгорнути дочірні компоненти в React.memo. React.memo — це компонент вищого порядку, який запам'ятовує результат функціонального компонента: якщо його властивості однакові між рендерами, React пропускає повторний рендеринг. Отже, ваш компонент зі списком навичок, після того, як його обгорнуто в React.memo, не буде повторно відображатися при кожному натисканні клавіші — лише тоді, коли skills фактичні зміни властивості (наприклад, коли ви додаєте нову навички).

Не всі дані, подібні до даних про стан, належать до useStateіноді useRef є кращим інструментом. Команда useRef Хук надає вам змінний об'єкт з current властивість, яка зберігається протягом усього терміну служби компонента, але її оновлення все ж таки НЕ запускати повторний рендеринг. Це робить його ідеальним для зберігання таких речей, як ідентифікатори таймерів, посилання на елементи DOM або лічильники, які ви хочете відстежувати, але не повинні відображатися в інтерфейсі користувача.

Простий приклад — лічильник, реалізований за допомогою useRef замість useState. Якщо ви зберігаєте лічильник у countRef.current та збільшуємо його в обробнику подій, внутрішнє значення змінюється, але відображений JSX не оновлюється, оскільки React не виконав повторний рендеринг. Це ілюструє ключову різницю: useState призначено для цінностей, які керують інтерфейсом користувача; useRef призначено для значень, які ви хочете зберегти, не впливаючи на рендеринг.

Незмінність і чому пряма мутація є пасткою

Фундаментальний принцип React полягає в тому, що оновлення стану повинні бути незмінними. Це не означає, що ви ніколи нічого не можете змінити; це означає, що замість того, щоб змінювати існуючі значення (особливо об'єкти та масиви), ви створюєте нові, а старі залишаєте як історичні знімки вашого інтерфейсу користувача.

Безпосередня мутація стану порушує зв'язок між вашою ментальною моделлю та тим, що робить React. Якщо ви зробите щось подібне state.count++ або завантаження безпосередньо в масив станів, React не знатиме, що щось змінилося, оскільки ви ніколи не викликали функцію updater. Внутрішній знімок, який React використовує для вирішення, коли повторно рендерити, залишається незмінним, тоді як ваш код вважає, що значення змінилося. Ось так виникають помилки, які «виправляються самі» під час перезавантаження.

Також потрібно уникати присвоєння значення стану іншій змінній, а потім зміни цієї змінної. Наприклад, роблячи const newCount = count; newCount++; все ще змінює те саме основне значення для примітивів та для об'єктів, const copy = stateObj; взагалі не створює копію — він просто створює інше посилання на той самий об'єкт. Для правильного копіювання потрібні такі шаблони, як { ...stateObj } для предметів або для масивів.

Такі бібліотеки, як Redux, MobX (якщо налаштовано на незмінність) або Immer, існують частково для забезпечення або спрощення незмінних шаблонів. Незалежно від того, чи використовуєте ви вбудовані хуки React, чи бібліотеку керування станами, золоте правило залишається в силі: ніколи не змінюйте існуючий стан, якщо очікуєте, що React сприйме зміни та повторно відрендерить їх.

Асинхронні оновлення, пакетна обробка та застарілий стан

Одна тонка, але важлива деталь щодо стану React полягає в тому, що оновлення є асинхронними та запланованими, а не застосовуються негайно. Коли ви телефонуєте setState або гачок-сеттер, як-от setCountReact «ставить у чергу» повторний рендеринг на деякий час у майбутньому. Він не блокує ваш код одразу для оновлення та повторного рендерингу, що дозволяє React пакетно виконувати кілька оновлень та підтримувати плавність продуктивності.

Ця модель планування означає, що ви не можете покладатися на зчитування стану одразу після виклику засобу оновлення всередині того самого синхронного блоку. Значення, яке ви отримаєте, зазвичай буде старим знімком. Натомість, вам слід думати про засіб оновлення як про запит: «наступного разу, коли ви будете рендерити, використовуйте це значення (або цю функцію перетворення)».

Це особливо важливо, коли ви оновлюєте стан на основі його поточного значення зсередини замикань, таких як setTimeout або зворотні виклики за підпискою. Ці зворотні виклики фіксують стан на момент їх створення. Якщо ви потім зробите це setCount(count + 1) всередині тайм-ауту, count про який ви маєте на увазі, може бути застарілим на момент фактичного виконання зворотного виклику.

Це явище відоме як «застарілий стан» або «застарілі закриття». Наприклад, якщо у вас є кнопка, яка при натисканні викликає функцію, що встановлює тайм-аут, а потім збільшує стан через одну секунду, кілька швидких кліків можуть неправильно збільшувати стан. Кожен зворотний виклик тайм-ауту використовує старий count воно було захоплено, коли було заплановано тайм-аут.

Надійним виправленням є використання функціональної форми оновлення вашого засобу встановлення стану. Замість setCount(count + 1) в межах тайм-ауту ви пишете setCount(prevCount => prevCount + 1)Тепер кожен зворотний виклик отримує останнє попереднє значення на момент застосування оновлення, а не те, яке було в області видимості, коли було створено тайм-аут. Це усуває проблему застарілого стану, не змінюючи поведінку ваших замикань в іншому випадку.

У документації React також вказується менш відома деталь: якщо ваш функціональний оновлювач нічого не повертає (undefined), React пропустить повторний рендеринг. Це означає, що ваші функції оновлення повинні завжди повертати наступне значення стану (або повторно використовувати попереднє), якщо ви явно не хочете запобігти оновленню — що рідко є бажаним зі стандартними useState використання.

Розуміння цієї комбінації асинхронного планування, пакетної обробки та поведінки закриття є критично важливим для написання надійної логіки станів у додатках, які мають справу з тайм-аутами, інтервалами, підписками або швидкою взаємодією з користувачем. Як тільки ви зрозумієте, що засоби встановлення станів планують оновлення, а не виконують їх негайно, помилки, які раніше здавалися випадковими, почнуть мати сенс.

Коли ви поєднаєте всі ці ідеї разом — props проти state, життєві цикли класів проти hooks, незмінність, контрольовані компоненти, useRef для невізуальних значень, мемоізації, асинхронних оновлень та застарілих замикань — ви отримуєте потужну, передбачувану модель того, як інтерфейси React розвиваються з часом. Замість того, щоб думати про імперативні зміни DOM, ви проєктуєте чіткі моделі станів і дозволяєте React обробляти повторний рендеринг, що спрощує обмірковування, тестування та розширення ваших компонентів у міру зростання вашої програми.

estado estable de javascript 2025
Пов'язана стаття:
Стабільний стан сучасного JavaScript
Схожі повідомлення: