API-довідник хуків

Хуки — це новинка в React 16.8. Вони дозволяють вам використовувати стан та інші можливості React без написання класу.

Ця сторінка описує API для вбудованих у React хуків.

Якщо ви новачок у хуках, ви можете спочатку переглянути огляд. Також ви можете знайти корисну інформацію у розділі FAQ.

Базові хуки

useState

const [state, setState] = useState(initialState);

Повертає значення стану та функцію, що оновлює його.

Під час початкового рендеру повернутий стан (state) співпадає зі значенням, переданим у першому аргументі (initialState).

Функція setState використовується для оновлення стану. Вона приймає значення нового стану і ставить у чергу повторний рендер компонента.

setState(newState);

Упродовж наступних повторних рендерів, перше значення, повернуте useState, завжди буде в актуальному стані при здійсненні оновлень.

Примітка

React гарантує, що функція setState зберігає ідентичність і не змінюється під час повторних рендерів. Саме тому, ви можете безпечно пропускати її включення до списків залежностей хуків useEffect чи useCallback.

Функціональні оновлення

Якщо наступний стан обчислюється з використанням попереднього, ви можете передати функцію до setState. Функція отримає попереднє значення і поверне оновлене. Ось приклад компонента лічильника, що використовує обидві форми setState:

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Скинути</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
    </>
  );
}

Кнопки ”+” та ”-” використовують функціональну форму, тому що оновлене значення базується на попередньому. В той же час кнопка “Скинути” використовує нормальну форму, тому що вона завжди скидає значення назад до початкового значення.

Примітка

На відміну від методу setState у класових компонентах, useState не об’єднує оновлювані об’єкти автоматично. Ви можете відтворити таку поведінку, комбінуючи функціональну форму оновлення і синтаксис розширення об’єктів:

setState(prevState => {
  // Object.assign також спрацює
  return {...prevState, ...updatedValues};
});

Іншим варіантом може бути хук useReducer, котрий більш підходить для керування об’єктами стану, що містять багато інших значень.

Лінива ініціалізація стану

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

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

Припинення оновлення стану

Якщо ви оновите стан хука значенням, що дорівнює поточному, React вийде з хука без рендерингу дочірніх елементів чи запуску ефектів. (React використовує алгоритм порівняння Object.is.)

Зверніть увагу, що React-у може знадобитись відрендерити конкретний компонент перед припиненням оновлення. Це не повинно викликати занепокоєння, тому що React необов’язково опуститься “глибше” в дерево. Якщо ви здійснюєте вартісні обчислення під час рендерингу, ви можете оптимізувати їх, використавши useMemo.

useEffect

useEffect(didUpdate);

Приймає функцію, що містить імперативний, можливо з ефектами, код.

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

Натомість застосовуйте useEffect. Функція, передана в useEffect, буде запущена після того, як вивід рендеру з’явиться на екрані. Думайте про ефекти як про засіб втечі з чисто функціонального світу React до світу імперативів.

За замовчуванням ефекти запускаються після кожного завершеного рендеру, але ви можете запускати їх, наприклад, коли змінились тільки певні значення.

Очищення ефектів

Ефекти часто створюють ресурси, пам’ять після використання яких, має бути звільнена перед тим, як компонент зникне з екрану, наприклад, підписка або ідентифікатор таймера. Щоб це зробити, функція, передана у useEffect, може повернути функцію очищення. Наприклад, щоб створити підписку:

useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    // Очистити підписку
    subscription.unsubscribe();
  };
});

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

Порядок спрацювання ефектів

На відміну від componentDidMount і componentDidUpdate, функція передана в useEffect запускається після розмітки та рендеру, протягом відкладеної події. Це робить хук підходящим для багатьох поширених побічних ефектів, таких як налаштування підписок та обробників подій, оскільки більшість типів роботи не повинні блокувати оновлення екрану браузером.

Проте не всі ефекти можуть бути відкладені. Наприклад, зміна DOM, що видима користувачу, має запуститись синхронно перед наступним рендером, щоб користувач не помічав візуальної невідповідності. (Ця відмінність концептально подібна до відмінності між пасивними та активними слухачами подій.) Для таких різновидів ефектів React надає додатковий хук, що зветься useLayoutEffect. Він має таку ж сигнатуру, як і useEffect, але відрізняється умовою запуску.

Незважаючи на те, що useEffect відкладається допоки браузер не виконає відображення, він гарантовано спрацює перед кожним новим рендером. React завжди застосовує ефекти попереднього рендеру перед початком нового оновлення.

Умовне спрацювання ефекту

За замовчуванням ефекти спрацьовуються після кожного завершеного рендеру. Таким чином, ефект завжди створюється повторно при зміні одної з його залежностей.

Проте, у деяких випадках, це може бути надлишковим, так само як у прикладі з підпискою у попередньому розділі. Нам не потрібно створювати нову підписку для кожного оновлення, а лише тоді, коли змінився проп source.

Щоб реалізувати це, передайте другим аргументом до useEffect масив значень, від яких залежить ефект. Наш оновлений ефект тепер виглядає так:

useEffect(
  () => {
    const subscription = props.source.subscribe();
    return () => {
      subscription.unsubscribe();
    };
  },
  [props.source],
);

Зараз підписка буде створена повторно лише при зміні props.source.

Примітка

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

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

Якщо ви передаєте порожній масив ([]), пропси і стан усередині ефекту будуть завжди мати їх початкові значення. Передача другим аргументом [], нагадує модель роботи вже знайомих componentDidMount та componentWillUnmount, але зазвичай є кращі рішення для уникнення частих повторних викликів ефектів. Також не забудьте, що React відкладає виконання useEffect до моменту відображення вмісту браузером, а отже можливість виконання додаткової роботи не є істотною проблемою.

Ми радимо використовувати правило exhaustive-deps, як частину нашого пакунку eslint-plugin-react-hooks. Воно попереджує про те, що залежності вказані невірно і пропонує рішення.

Масив залежностей не передається у якості аргументів до функції ефекту. Концептуально, проте, це те, що вони представляють: кожне значення, на яке посилається функція ефекту, також має з’являтись у масиві залежностей. У майбутньому, достатньо просунутий компілятор зможе створити цей масив автоматично.

useContext

const value = useContext(MyContext);

Приймає об’єкт контексту (значення, повернуте з React.createContext) і повертає поточне значення контексту для нього. Поточне значення контексту визначається пропом value найближчого <MyContext.Provider>, що знаходиться вище у дереві компонентів.

Коли найближчий <MyContext.Provider>, що знаходиться вище поточного компонента, оновлюється, цей хук викличе повторний рендер з актуальним value контексту, переданим до провайдера MyContext.

Не забудьте про те, що аргумент переданий у useContext, повиен бути власне об’єктом контексту:

  • Правильно: useContext(MyContext)
  • Неправильно: useContext(MyContext.Consumer)
  • Неправильно: useContext(MyContext.Provider)

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

Порада

Якщо ви ознайомились з API контексту до хуків, useContext(MyContext) є еквівалентним static contextType = MyContext у класі чи <MyContext.Consumer>.

useContext(MyContext) дозволяє лише читати контекст і підписуватись на його зміни. Вам і досі необхідно мати <MyContext.Provider> вище у дереві, щоб надати цьому контексту значення.

Давайте використаємо Context.Provider

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);

  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

Цей приклад – версія прикладу з документації контекста, адаптована под хуки. Там ви зможете знайти більше інформації про те, як і коли використовувати контекст.

Додаткові хуки

Наступні хуки є або варіантами базових із розділу вище, або потрібні у вкрай специфічних випадках. Не потрібно вивчати їх наперед.

useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init);

Є альтернативою useState. Приймає редюсер типу (state, action) => newState і повертає поточний стан у парі з методом dispatch. (Якщо ви знайомі з Redux, ви вже знаєте як це працює.)

Слід віддати перевагу useReducer над useState, коли ви маєте складну логіку стану, що включає багато значень чи ваш наступний стан залежить від попереднього. Крім того, useReducer дозволяє вам оптимізувати продуктивність для компонентів, що викликають глибокі оновлення, тому що ви можете передати вниз dispatch, замість функцій повторного виклику.

Ось приклад лічильника із розділу useState, переписаний із використанням редюсера:

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

Примітка

React гарантує, що функція dispatch зберігає ідентичність і не змінюється під час повторних рендерів. Саме тому, ви можете безпечно пропускати її включення до списків залежностей хуків useEffect чи useCallback.

Визначення початкового стану

Є два різних шляхи ініціалізації стану useReducer. Ви можете обрати будь-який, залежно від ситуації. Найпростішим варіантом є передача початкового стану другим аргументом:

  const [state, dispatch] = useReducer(
    reducer,
    {count: initialCount}
  );

Примітка

React не використовує state = initialState конвенцію про аргументи, популяризовану в Redux. Початкове значення часом залежить від пропсів, а тому вказується безпосередньо у виклиці хука. Якщо ви впевнені щодо цього, ви можете викликати useReducer(reducer, undefined, reducer), щоб земулювати поведінку Redux, але робити так не рекомендується.

Лінива ініціалізація

Ви також можете ліниво створити початковий стан. Щоб зробити це, ви можете передати функцію init третім аргументом. Початковий стан буде встановлений у init(initialArg).

Це дозволить винести логіку обчислення початкового стану з редюсера. Також це може бути корисним при скиданні стану пізніше у відповідь на дію:

function init(initialCount) {
  return {count: initialCount};
}

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    case 'reset':
      return init(action.payload);
    default:
      throw new Error();
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  return (
    <>
      Лічильник: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>
        Скинути
      </button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

Припинення дії dispatch

Якщо ви повернете з хука редюсера значення, що дорівнює поточному стану, React вийде з нього без рендерингу дочірніх елементів чи запуску ефектів. (React використовує алгоритм порівняння Object.is.)

Зверніть увагу, що React-у може знадобитись відрендерити конкретний компонент перед припиненням оновлення. Це не повинно викликати занепокоєння, тому що React необов’язково опуститься “глибше” в дерево. Якщо ви здійснюєте вартісні обчислення під час рендерингу, ви можете оптимізувати їх, використавши useMemo.

useCallback

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

Повертає мемоізовану функцію зворотнього виклику.

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

useCallback(fn, deps) є еквівалентом useMemo(() => fn, deps).

Примітка

Масив залежностей не передається у якості аргументів до функції зворотнього виклику. Концептуально, проте, це те, що вони представляють: кожне значення, на яке посилається функція зворотнього виклику, також має з’являтись у масиві залежностей. У майбутньому, достатньо просунутий компілятор зможе створити цей масив автоматично.

Ми радимо використовувати правило exhaustive-deps, як частину нашого пакунку eslint-plugin-react-hooks. Воно попереджує про те, що залежності вказані невірно і пропонує рішення.

useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

Повертає мемоізоване значення.

Передайте функцію “створення” та масив залежностей. useMemo повторно обчислить мемоізоване значення лише при зміні однієї з залежностей. Така оптимізація допомагає уникнути вартісних обчислень при кожному рендері.

Пам’ятайте, що функція, передана до useMemo, запускається під час рендерингу. Не робіть у ній нічого, що ви зазвичай не робите під час рендерингу. Наприклад, побічні ефекти мають бути в useEffect, а не useMemo.

Якщо масив не наданий, нове значення буде обчислене при кожному рендері.

Ви можете покластись на useMemo як на оптимізацію продуктивності, а не на семантичу гарантію. У майбутньому React може вирішити “забути” деякі попередньо мемоізовані значення і переобчислити їх при наступному рендері, наприклад, для звільнення пам’яті для компонентів поза областю видимості екрана. Напишіть ваш код так, щоб він працював без useMemo, а потім додайте його для оптимізації продуктивності.

Примітка

Масив залежностей не передається у якості аргументів до функції. Концептуально, проте, це те, що вони представляють: кожне значення, на яке посилається функція, також має з’являтись у масиві залежностей. У майбутньому, достатньо просунутий компілятор зможе створити цей масив автоматично.

Ми радимо використовувати правило exhaustive-deps, як частину нашого пакунку eslint-plugin-react-hooks. Воно попереджує про те, що залежності вказані невірно і пропонує рішення.

useRef

const refContainer = useRef(initialValue);

useRef поверне змінний об’єкт рефу, властивість .current якого ініціалізується переданим аргументом (initialValue). Повернутий об’єкт буде зберігатись протягом всього часу життя компонента.

Поширеним випадком використання є імперативний доступ до потомків:

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` вказує на примонтований елемент поля вводу тексту
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Фокусуватись на полі вводу</button>
    </>
  );
}

По суті, useRef — це “коробка”, що може містити змінне значення у власній властивості .current.

Рефи можуть бути вам знайомі перш за все як засіб доступу до DOM. Якщо ви передасте об’єкт рефу у React як <div ref={myRef} />, React встановить його властивість .current рівною значенню відповідного DOM вузла при будь-якій зміні цього вузла.

Проте useRef() є корисним не тільки для простого атрибута ref. Він згодиться для постійного збереження будь-якого змінного значення подібно до використання полей екземпляра класу.

Це можливо, тому що useRef() створює простий JavaScript-об’єкт. Єдина різниця між useRef() і створенням об’єкта {current: ...} власноруч полягає в тому, що useRef поверне один і той самий реф-об’єкт при кожному рендері.

Пам’ятайте, що useRef не повідомляє вас про зміну свого вмісту. Зміна властивості .current не спричинить повторний рендер. Якщо ви хочете запустити деякий код під час того, як React прикріплює чи від’єднує реф до вузла DOM, то вам краще використати реф зворотнього виклику.

useImperativeHandle

useImperativeHandle(ref, createHandle, [deps])

useImperativeHandle налаштовує значення екземпляра, яке надається батьківським компонентам при використанні ref. Як і зазвичай, у більшості випадків ви маєте уникати імперативного коду з використанням рефів. useImperativeHandle має використовуватись разом з forwardRef:

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

У цьому прикладі батьківський компонент, що рендерить <FancyInput ref={fancyInputRef} />, матиме змогу викликати fancyInputRef.current.focus().

useLayoutEffect

Сигнатура хука є ідентичною useEffect, але він запускається синхронно після всіх змін DOM. Використовуйте це для читання розмітки з DOM і синхронних повторних рендерів. Оновлення, заплановані всередині useLayoutEffect, будуть виконані синхронно перед тим, як браузер відобразить їх.

Віддайте перевагу використанню useEffect, коли це можливо, щоб уникнути блокування візуальних оновлень.

Порада

Якщо ви переробляєте код класового компонента, зверніть увагу, що useLayoutEffect запускається на тому ж етапі, що й componentDidMount та componentDidUpdate. Однак ми рекомендуємо розпочати з використання useEffect і спробувати useLayoutEffect тільки при виникненні проблем.

Якщо ви використовуєте серверний рендеринг, пам’ятайте, що ані useLayoutEffect, ні useEffect не можуть бути запущені допоки JavaScript не завантажився. Саме тому React попереджує про те, що компонент, який рендериться на сервері, містить useLayoutEffect. Щоб виправити це, ви можете помістити цю логіку в useEffect (якщо вона не потрібна при першому рендері) або відкласти відображення цього компонента до моменту, коли відрендериться клієнт (якщо HTML виглядає невалідним до запуску useLayoutEffect).

Щоб виключити компонент, що потребує ефектів розмітки, із відрендереного на сервері HTML, відрендеріть його умовно з допомогою showChild && <Child /> і відкладіть його відображення, використавши useEffect(() => { setShowChild(true); }, []). Таким чином, інтерфейс користувача не буде виглядати неправильно перед гідратацією.

useDebugValue

useDebugValue(value)

useDebugValue може бути використаний для відображення мітки користувацьких хуків у інструментах розробника React.

Наприклад, ми маємо користувацький хук useFriendStatus описаний у розділі “Створення користувацьких хуків”:

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // ...

  // Показує мітку у інструментах розробника поруч з цим хуком
  // наприклад, "FriendStatus: В мережі"
  useDebugValue(isOnline ? 'В мережі' : 'Не в мережі');

  return isOnline;
}

Порада

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

Відкладення форматування значень для налагодження

У певних випадках, форматування значення для відображення може бути вартісною операцією. Також воно не потрібне у випадку, коли хук не перевіряється безпосередньо.

З цієї причини useDebugValue приймає функцію форматування у якості необов’язкового другого параметра. Ця функція викликається лише при перевірці хуків. Вона отримує налагоджувальне значення у якості параметра і повинна повертати форматоване значення для відображення.

Для прикладу, користувацький хук, що повертає значення Date, може уникнути викликання функції toDateString, передавши наступну функцію форматування:

useDebugValue(date, date => date.toDateString());