Spec-Zone.ru › React 17

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

Хуки — это новое дополнение в React 16.8. Они позволяют использовать состояние и другие возможности React без написания класса.

Создание собственных хуков позволяет извлекать логику компонента в переиспользуемые функции.

Когда мы изучали использование хука Effect, мы увидели этот компонент из чат-приложения, который отображает сообщение, указывающее, онлайн или офлайн друг:

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

function FriendStatus(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);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

Предположим теперь, что наше чат-приложение также имеет список контактов, и мы хотим отображать имена онлайн-пользователей зелёным цветом. Мы могли бы скопировать и вставить аналогичную логику выше в наш FriendListItem компонент, но это не было бы идеально:

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>
  );
}

Вместо этого мы хотели бы поделиться этой логикой между FriendStatus и FriendListItem.

Традиционно в React у нас было два популярных способа совместного использования состояний между компонентами: render props и higher-order components. Теперь мы рассмотрим, как хуки решают многие из тех же проблем без принуждения к добавлению дополнительных компонентов в дерево.

Извлечение пользовательского хука

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

Пользовательский хук — это функция JavaScript, имя которой начинается с «use», и которая может вызывать другие хуки. Например, useFriendStatus ниже — наш первый пользовательский хук:

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;
}

В нём нет ничего нового — логика скопирована из компонентов выше. Как и в компоненте, убедитесь, что вызываете другие хуки только безусловно на верхнем уровне вашего пользовательского хука.

В отличие от компонента React, пользовательский хук не требует определённой подписи. Мы можем решить, что он принимает в качестве аргументов и что, если что-либо, он должен вернуть. Другими словами, это просто как обычная функция. Его имя всегда должно начинаться с use, чтобы вы сразу понимали, что к нему применяются правила хуков.

Цель нашего useFriendStatus хука — подписаться на статус друга. Вот почему он принимает friendID в качестве аргумента и возвращает, онлайн ли этот друг:

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

  // ...

  return isOnline;
}

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

Использование пользовательского хука

Вначале нашей целью было устранить дублированную логику из FriendStatus и FriendListItem компонентов. Оба они хотят знать, онлайн ли друг.

Теперь, когда мы извлекли эту логику в 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.

Должен ли я называть свои пользовательские хуки, начиная с «use»? Да, обязательно. Это соглашение очень важно. Без него мы не смогли бы автоматически проверять нарушения правил хуков, потому что не смогли бы определить, содержит ли та или иная функция вызовы хуков внутри себя.

Два компонента, использующие один и тот же хук, разделяют состояние? Нет. Пользовательские хуки — это механизм для повторного использования логики состояния (например, настройки подписки и запоминания текущего значения), но каждый раз, когда вы используете пользовательский хук, всё состояние и эффекты внутри него полностью изолированы.

Как пользовательский хук получает изолированное состояние? Каждое вызов хука получает изолированное состояние. Поскольку мы вызываем useFriendStatus напрямую, с точки зрения React наш компонент просто вызывает useState и useEffect. И, как мы узнали ранее, мы можем вызывать useState и useEffect многократно в одном компоненте, и они будут полностью независимы.

Подсказка: передача информации между хуками

Поскольку хуки — это функции, мы можем передавать информацию между ними.

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

const friendList = [
  { id: 1, name: 'Phoebe' },
  { id: 2, name: 'Rachel' },
  { id: 3, name: 'Ross' },
];

function ChatRecipientPicker() {
  const [recipientID, setRecipientID] = useState(1);
  const isRecipientOnline = useFriendStatus(recipientID);

  return (
    <>
      <Circle color={isRecipientOnline ? 'green' : 'red'} />
      <select
        value={recipientID}
        onChange={e => setRecipientID(Number(e.target.value))}
      >
        {friendList.map(friend => (
          <option key={friend.id} value={friend.id}>
            {friend.name}
          </option>
        ))}
      </select>
    </>
  );
}

Мы сохраняем идентификатор текущего выбранного друга в recipientID переменной состояния и обновляем её, если пользователь выбирает другого друга в <select> выборе.

Поскольку вызов useState хука даёт нам последнее значение recipientID переменной состояния, мы можем передать его нашему пользовательскому useFriendStatus хуку в качестве аргумента:

  const [recipientID, setRecipientID] = useState(1);
  const isRecipientOnline = useFriendStatus(recipientID);

Это позволяет нам узнать, онлайн ли текущий выбранный друг. Если мы выберем другого друга и обновим recipientID переменную состояния, наш useFriendStatus хук отменит подписку на ранее выбранного друга и подпишется на статус нового.

useYourImagination()

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

Постарайтесь не добавлять абстракцию слишком рано. Теперь, когда функциональные компоненты могут делать больше, средний функциональный компонент в вашем коде, вероятно, станет длиннее. Это нормально — не думайте, что вам обязательно нужно сразу же разделить его на хуки. Но мы также рекомендуем начинать замечать случаи, когда пользовательский хук может скрыть сложную логику за простым интерфейсом или помочь распутать беспорядочный компонент.

Например, возможно, у вас есть сложный компонент, содержащий много локального состояния, которое управляется нестандартным способом. useState не упрощает централизацию логики обновления, поэтому вы можете предпочесть написать его в виде Redux редьюсера:

function todosReducer(state, action) {
  switch (action.type) {
    case 'add':
      return [...state, {
        text: action.text,
        completed: false
      }];
    // ... other actions ...
    default:
      return state;
  }
}

Редьюсеры очень удобны для тестирования в изоляции и масштабируются для выражения сложной логики обновления. При необходимости вы можете разбить их на более мелкие редьюсеры. Однако вы также можете получить преимущества использования локального состояния React или не захотите устанавливать ещё одну библиотеку.

А что, если мы могли бы написать useReducer хук, который позволит нам управлять локальным состоянием нашего компонента с помощью редьюсера? Упрощённая версия может выглядеть так:

function useReducer(reducer, initialState) {
  const [state, setState] = useState(initialState);

  function dispatch(action) {
    const nextState = reducer(state, action);
    setState(nextState);
  }

  return [state, dispatch];
}

Теперь мы могли бы использовать его в нашем компоненте и позволить редьюсеру управлять его состоянием:

function Todos() {
  const [todos, dispatch] = useReducer(todosReducer, []);

  function handleAddClick(text) {
    dispatch({ type: 'add', text });
  }

  // ...
}

Необходимость управления локальным состоянием с помощью редьюсера в сложном компоненте достаточно распространена, поэтому мы создали useReducer хук прямо в React. Вы найдёте его вместе с другими встроенными хуками в справочнике по API хуков.

Полезная ли эта страница?

© 2013–present Facebook Inc.
Licensed under the Creative Commons Attribution 4.0 International Public License.
https://17.reactjs.org/docs/hooks-custom.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API