С тех пор, как был представлен API перехватчиков React, возникло много вопросов о том, заменят ли перехватчики React другие распространенные библиотеки и шаблоны в экосистеме React + Redux.

Хуки были разработаны, чтобы заменить class и предоставить еще одну отличную альтернативу для компоновки поведения в ваших компонентах. Компоненты более высокого порядка также полезны для составления поведения. Есть некоторые очевидные совпадения, так что заменяют ли перехватчики React компоненты высшего порядка? Совершенно очевидно, что они могут заменить некоторые HOC. Но стоит ли вам заменить все ваши HOC на перехватчики React?

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

Что такое компоненты высшего порядка?

Компонент более высокого порядка (HOC) - это компонент, который принимает компонент и возвращает компонент. HOC можно компоновать с помощью декларативной композиции функций без точек. Вот пример, в котором каждый просмотр страницы будет регистрироваться в /logger API:

import React, { useEffect } from 'react';
const withLogging = Component => props => {
  useEffect(() => {
    fetch(`/logger?location=${ window.location}`);
  }, []);
  return <Component {...props } />;
};
export default withLogging;

Чтобы использовать его, вы можете смешать его в HOC, который будет обтекать каждую страницу:

import compose from 'ramda';
import withRedux from './with-redux.js';
import withAuth from './with-auth.js';
import withLogging from './with-logging.js';
import withLayout from './with-layout.js';
const page = compose(
  withRedux,
  withAuth,
  withLogging,
  withLayout('default'),
);
export default page;

Это создает иерархию компонентов, которую вы можете представить себе так:

<withRedux>
  <withAuth>
    <withLogging>
      <withLayout>
        <MyPageComponent />
      </withLayout>
    </withLogging>
  </withAuth>
</withRedux>

Чтобы использовать это для страницы:

import page from '../hocs/page.js';
import MyPageComponent from './my-page-component.js';
export default page(MyPageComponent);

Это отличный паттерн, если:

  • HOC не нужно создавать более одной опоры для передачи дочерним компонентам. Желательно, чтобы они вообще не создавали никакого реквизита.
  • HOC не создает неявных зависимостей, на которые полагаются другие HOC или компоненты.
  • Все или многие компоненты вашего приложения должны будут разделять поведение.

Примечание. Это не жесткие правила, от которых вы никогда не должны отступать. Вместо этого они представляют собой практические правила, которые обычно хорошо вам служат. Я часто делаю небольшое исключение из практического правила «отсутствие неявных зависимостей» для HOC, который предоставляет мой провайдер Redux. Я называю это withRedux. После подключения Redux другие HOC могут получить доступ к состоянию для авторизации пользователей и т. Д.

Шаблон составления HOC для функциональности, разделяемой всеми вашими страницами, по-прежнему является лучшим подходом, который я знаю, для решения многих сквозных проблем, таких как общий макет, ведение журнала, аутентификация / авторизация и все остальное, что является общим, кроме не требует никакой логики, связанной с конкретным компонентом.

Зачем использовать HOC?

Основное преимущество HOC заключается не в том, что они позволяют (есть другие способы сделать это), а в том, как они объединяются на уровне корня страницы. В отличие от хуков, HOC могут быть составлены декларативно с использованием стандартной композиции функций, например:

const page = compose(
  withRedux,
  withAuth,
  withLogging,
  withLayout('default'),
);

Если вы полностью исключите HOC, другие параметры (например, хуки и реквизиты) потребуют специальной композиции, требующей большого количества дублирования кода и множества разовых реализаций одной и той же логики, распространяемых по всему вашему приложению и добавляемых к компонентам, которые не должны быть обеспокоены ими, нарушая некоторые фундаментальные принципы проектирования программного обеспечения, в том числе:

Помните следующее, когда вы используете HOC, потому что при неправильном использовании HOC могут создавать проблемы:

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

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

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

Вот пример реального компонента, использующего хуки:

import React, { useState } from 'react';
import t from 'prop-types';
import TextField, { Input } from '@material/react-text-field';
const noop = () => {};
const Holder = ({
  itemPrice = 175,
  name = '',
  email = '',
  id = '',
  removeHolder = noop,
  showRemoveButton = false,
}) => {
  const [nameInput, setName] = useState(name);
  const [emailInput, setEmail] = useState(email);
  const setter = set => e => {
    const { target } = e;
    const { value } = target;
    set(value);
  };
  return (
    <div className="row">
      <div className="holder">
        <div className="holder-name">
          <TextField label="Name">
            <Input value={nameInput} onChange={setter(setName)} required />
          </TextField>
        </div>
        <div className="holder-email">
          <TextField label="Email">
            <Input
              value={emailInput}
              onChange={setter(setEmail)}
              type="email"
              required
            />
          </TextField>
        </div>
        {showRemoveButton && (
          <button
            className="remove-holder"
            aria-label="Remove membership"
            onClick={e => {
              e.preventDefault();
              removeHolder(id);
            }}
          >
            &times;
          </button>
        )}
      </div>
      <div className="line-item-price">${itemPrice}</div>
      <style jsx>{cssHere}</style>
    </div>
  );
};
Holder.propTypes = {
  name: t.string,
  email: t.string,
  itemPrice: t.number,
  id: t.string,
  removeHolder: t.func,
  showRemoveButton: t.bool,
};
export default Holder;

Этот код использует useState для отслеживания состояний ввода эфемерной формы для имени и электронной почты:

const [nameInput, setName] = useState(name);
const [emailInput, setEmail] = useState(email);

Это состояние используется только для этого компонента, поэтому хуки хорошо подходят для этого варианта использования.

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

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

У вас может быть плохой вариант использования HOC, если:

  • Для этого поведения требуется добавить к компоненту набор свойств.
  • Такое поведение используется только в одном компоненте.
  • Поведение необходимо настраивать для каждого компонента, который его использует.

У вас может быть хороший пример использования HOC, если:

  • Такое поведение не относится к какому-либо отдельному компоненту, а, скорее, применяется ко многим или всем компонентам в приложении и
  • Поведение не должно предоставлять набор свойств для компонентов, которые его используют.
  • Компоненты можно использовать автономно без участия HOC.
  • Никакой пользовательской логики не требуется добавлять к компоненту, который оборачивается HOC.

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

Эрик Эллиотт - автор книг Создание программного обеспечения и Программирование приложений JavaScript. Как соучредитель EricElliottJS.com и DevAnywhere.io, он обучает разработчиков основным навыкам разработки программного обеспечения. Он создает и консультирует команды разработчиков для криптопроектов, а также участвовал в разработке программного обеспечения для Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC и ведущие музыканты, в том числе Ашер, Фрэнк Оушен, Metallica и многие другие.

Он ведет уединенный образ жизни с самой красивой женщиной в мире.