Включите исполнение JavaScript в браузере, чтобы запустить приложение.

Хуки (React Hooks): что это такое в программировании

6 сен 2024
Рассказываем о том, что такое React Hooks простыми словам. Разбираем основные: useState, useEffect, useContext и дополнительные хуки: useReducer, useCallback, useLayoutEffect, useMemo, useRef и т.д. Также поговорим о том, как создавать собственные хуки.

React hooks помогают пользоваться функциями React без создания нового компонента. С помощью хуков можно «подключаться» к состоянию и функциям жизненного цикла React из специальных компонентов. В материале подробно расскажем о том, что такое хуки, какие виды React hooks есть в программировании и как самостоятельно их создавать. 

  • Хуки React: что это
  • Основные хуки
  • Дополнительные хуки
  • Правила хуков
  • Как создавать собственные хуки
  • Заключение

Хуки React: что это

В программировании с React работают фронтенд-разработчики, тестировщики, верстальщики. Все они создают пользовательские веб-интерфейсы. React как библиотека JavaScript помогает создавать приложения и сайты. 

Хуки — это функции, которые упрощают работу с компонентами React. Они дают доступ к методам из функциональных компонентов. Hooks позволяют работать в React без классов. Они нужны для:

  • создания многоразовой логики с сохранением состояния. Используя хуки, можно извлечь логику с сохранением состояния из компонента и поместить в другую функцию, которую можно использовать в других компонентах. Так код может стать более простым и структурированным;
  • написания производительного кода. Хуки делают объем кода меньше, это может снизить нагрузку на сервер;
  • разработки более простого кода. Hooks повышают модульность циклов. Благодаря этому готовый код легче поддерживать, вносить в него изменения.

React hook есть в React версии 16.8.0 или выше. В уже готовых решениях на React не обязательно менять все существующие классы на хуки. Авторы библиотеки JavaScript рассчитывают на то, что hooks смогут быть такими функциональными, как и классы. Сейчас пока что нет вариантов, которые могут работать с методами жизненного цикла. Зато существующие версии хуков справляются с поставленными задачами — они облегчают процесс написания кода, делают его более производительным. 

Основные хуки

Разберем основные хуки: useState, useEffect, useContext. 

useState

Создан для управления состоянием компонентов. Он принимает стартовое значение в виде данных и выводит массив, содержащий значение состояния и функцию для его обновления.Вызов use state нужен для возвращения значения и его обновления. Технология подходит для использования в разных моделях, ее плюс в том, что прошлое и новое состояние не сливаются.Хук useState() берет первое значение переменной состояния в качестве аргумента. Затем второе значение устанавливает новое состояние, поэтому оно всегда инициируется как setState.

const Signin = () => {

  return (

    <div>

        <div>

            <button type="button">Sign Out</button>

            <p>Welcome back, good to see you in here<p>

        </div>

        <div>

            <button type="button">Sign In</button>

            <p>Please Sign in</p>

        </div>

    </div>

  )

}

export default Signin;

В примере хук обновляет состояние в зависимости от двух условий: вошел пользователь в систему или нет. Для стартового состояния установлено значение false — пользователь не вошел в систему.

Можно сделать кнопку входа в систему, которая будет использовать хук useState() для отображения двух разных результатов.

Одна из них — кнопка входа с сообщением, предлагающим пользователю войти в систему. Другая — кнопка, которая после входа пользователя в систему дает ему возможность выйти оттуда.

const Signin = () => {

    const [signedin, setSignedin] = useState(false)

    const handleSignin = () => {

        setSignedin(true)

    }

    const handleSignout = () => {

        setSignedin(false)

    }

  return (

         <div>

           { signedin ? ( 

        <div>

            <button type="button" onClick={handleSignout}>Sign Out</button>

            <p>Welcome back, good to see you in here</p>

        </div>) :

        (<div>

            <button type="button"onClick={handleSignin}>Sign In</button>

            <p>Please Sign in</p>

        </div>)

           }

        </div>

  )

}

export default Signin;

В новом примере создали переменную с помощью use State(), которая устанавливает для Signin значение false. При первой загрузке не надо, чтобы пользователь входил в систему. Но как только он нажмет кнопку входа, он сможет «войти».

Также обратите внимание, что хук useState() импортирован наверху.

Затем идут переменные, которые обрабатывают вход в систему, выход из системы и присваивают функции set значения true и false соответственно — это handleSignin и handleSignout.

После этого в работу вступает обработчик onClick, анализирующий нажатие кнопки и запускающий действие. Этим действием управляет условный оператор.

useEffect

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

Извлечение данных, контроль подписок, ручное редактирование DOM и настройка таймера — примеры использования этого хука. Функция и необязательный массив зависимостей — два аргумента, необходимые для useEffect. Этот инструмент часто используется для управления различными событиями, не требующими рендеринга компонента.

function counter(props) {

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

  useEffect(() => {

    setState(0);

  }, []);

  return (

      <div> Count: {state} </div>

  );

}

useContext

Hook дает возможность обмениваться данными между компонентами без детализации. Он предоставляет способ передачи данных через дерево компонентов без передачи реквизитов вручную на каждом уровне.

Пример:

import React, { useContext } from 'react'

const CurrentRoute = React.createContext({ path: '/dashboard' })

const CurrentUser = React.createContext(undefined)

const IsStatic = React.createContext(false)

export default function App() {

  let currentRoute = useContext(CurrentRoute)

  let currentUser = useContext(CurrentUser)

  let isStatic = useContext(IsStatic)

  return (

    !isStatic &&

    currentRoute.path === '/dashboard' &&

    (currentUser

      ? `Добрый день, ${currentUser.name}!`

      : 'Добрый день!'

    )

  )

}

Дополнительные хуки

useReducer

Хук используется для обработки состояния. В приложениях To-Do можно добавлять, удалять и обновлять элементы в списке дел. Сама операция обновления может включать обновление элемента или пометку его как завершенного.

При работе со списком дел активизируется переменная состояния todoList. Состояние будет обновляться для выполнения каждой операции. Однако обновления могут появляться в разных местах, иногда даже не внутри компонента.

Чтобы сделать код более читабельным, можно переместить все обновления состояния в одну функцию, в том числе работающую отдельно от компонента. При выполнении необходимых операций компоненту достаточно вызвать один метод и выбрать операцию, которую он хочет выполнить. Функция, содержащая все обновления состояния, называется reducer. Метод вызова для выполнения операций — dispatch method.

Пример:

const handleUpdateUser = (updatedUser) => {

    dispatch({

        type: 'updateUser',

        updatedUser: updatedUser

    })

}

const handleDeleteUser = (userId) => {

    dispatch({

        type: 'deleteUser',

        id: userId

    })

}

useCallback

Функция оптимизации скорости, помогающая запоминать методы обратного вызова. С помощью нее их не приходится создавать заново при каждом рендеринге. В качестве параметров нужны функция и необязательный массив зависимостей. Обратный вызов, необходимый для запоминания, — это функция. 

const Counter = () => {

const handleClick = useCallback { () => {

} , [] );

return (

<div>

<button onClick={handleClick}>Clickme</button>

<div>

)

};

useLayoutEffect

React hook, который работает по системе, похожей на useEffect, но запускается синхронно после завершения всех модификаций DOM. Нужен для измерения элементов DOM, запуска анимации или повторного рендеринга DOM в ответ на изменения макета. В любом из процессов важно, чтобы DOM был согласован. 

const AnimatingElements = () => {

 const elementRef = useRef(null);

 useLayoutEffect(() => {

   const element = elementRef.current;

   // Animate the element's opacity on mount

   element.style.opacity = 0;

   setTimeout(() => {

     element.style.opacity = 1;

   }, 1000);

   return () => {

     // Clean up animation when the component unmounts

     element.style.opacity = 0;

   };

 }, []);

 return <div ref={elementRef}>Animate me!</div>;

};

В коде непрозрачность элемента анимируется с помощью useLayoutEffect. Стартовая непрозрачность элемента равна 0. Затем функция setTimeout меняет ее на 1 после клика в 1000 мс.

Хук применяет анимацию после запуска компонента. Непрозрачность элемента сбрасывается до 0, когда компонент выключается.

useMemo

Помогает управлять кэшированными параметрами. Вычисление для запоминания называется функцией. Значения, от которых оно зависит, указываются в массиве зависимостей. Вычисление будет повторяться. Значение обновится, если изменится какая-либо из зависимостей. В ином случае кэшированное значение возвратится в начальном виде.

import React, { useMemo } from 'react';

const UserComponent = ({ firstName, lastName }) => {

  const fullName = useMemo(() => {

    return `${firstName} ${lastName}`;

  }, [firstName, lastName]);

  return <div>{fullName}</div>;

};

export default UserComponent;

useRef

Может возвращать и сохранять объект, который легко изменить в любое время существования компонента. Hook принимает один аргумент в качестве начального значения и возвращает ссылку (ее еще называют ref). Ссылка — это объект со специальным свойством current.

import { useRef } from 'react';

function MyComponent() {

 const initialValue = 0;

 const reference = useRef(initialValue);

 const someHandler = () => {

   // Access reference value:

   const value = reference.current;

   // Update reference value:

   reference.current = newValue;

 };

 // ...

}

В примере reference.current обращается к ссылочному значению, а reference.current = newValue обновляет его. Важно, что ссылка остается прежней между повторными рендерингами компонента, а обновление ссылки не вызывает повторную отрисовку компонента.

Правила хуков

Важные правила хуков:

  • Вызов доступен только из функциональных компонентов React. Нельзя вызвать hooks внутри компонентов класса или функций.
  • Возможность вызова доступна лишь на верхнем уровне. Не рекомендуется использование хуков внутри функций или циклов. Чтобы React правильно отображал components, ему нужно знать, какие хуки и в какой последовательности вызываются.
  • Вызывать хуки можно в одинаковом порядке при каждом рендеринге. Это нужно, чтобы React мог правильно управлять состоянием и настройками, связанными с каждым хуком.

Как создавать собственные хуки

Рассмотрим создание хука на примере расширения, которое будет отображать имена онлайн-пользователей социальной сети зелеными.

import React, { useState, useEffect } from 'react';

function FriendListItem(props) {

 const [isOnline, setIsOnline] = useState(null);

 useEffect(() => {

   function handleStatusChange(status) {

     setIsOnline(status.isOnline);

   }

   ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);

   return () => {

     ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);

   };

 });

  return (

    <li style={{ color: isOnline ? 'green' : 'black' }}>

      {props.friend.name}

    </li>

  );

}

Когда мы хотим разделить логику между двумя функциями JavaScript, мы извлекаем ее в третью функцию. И компоненты, и хуки — функции, поэтому в случае с ними это работает.

import { useState, useEffect } from 'react';

function useFriendStatus(friendID) {

 const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {

    function handleStatusChange(status) {

      setIsOnline(status.isOnline);

    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);

    return () => {

      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);

    };

  });

  return isOnline;

}

Нужно убедиться, что вызов других хуков происходит только на верхнем уровне основного.

В отличие от компонента hook не требует конкретной подписи. Можно выбрать, что он принимает в качестве аргументов и что должен возвращать (если это нужно). Его имя всегда должно начинаться с use, чтобы сразу можно было понять: здесь применяются правила хуков.

Цель useFriendStatus — отслеживать статус друга, чтобы понимать, когда он онлайн. Хук принимает идентификатор друга в качестве аргумента и возвращает ответ — в сети ли он сейчас. 

Итоговый вариант:

function FriendStatus(props) {

 const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {

    return 'Loading...';

  }

  return isOnline ? 'Online' : 'Offline';

}

function FriendListItem(props) {

 const isOnline = useFriendStatus(props.friend.id);

  return (

    <li style={{ color: isOnline ? 'green' : 'black' }}>

      {props.friend.name}

    </li>

  );

}

Получившийся код работает точно так же, как и стартовый. Общий код двух функций образовал отдельную функцию.

Заключение

React hooks помогают разделить код на более производительные функции. Это облегчает чтение, понимание и тестирование кода. Вместо того, чтобы вручную вводить данные на каждом уровне, можно передавать данные вниз по дереву компонентов. С помощью хуков проще написать более короткий код. В компонентах React хуки предлагают стандартизированный метод обработки состояния и выполнения эффектов. Так улучшается читаемость кода.