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

Начиная с этой недели с выпуском React v16.8, теперь вы можете добавлять хуки в свои проекты!

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

Терминология

Примечание: пропустите этот раздел, если вы знакомы с классами и функциональными компонентами React.

Я собираюсь использовать термины «класс» и «функциональные» компоненты для обозначения современных вариантов создания компонентов. Компонент класса выглядит примерно так:

class ClassExample extends React.Component {
  state = {
    greeting: 'Hello'
  }
  render() {
    return (
      <span>
        {this.state.greeting}, {this.props.name}!
      </span>
    )
  }
}

Компоненты класса имеют доступ к внутреннему состоянию; методы по умолчанию, настраиваемые и методы жизненного цикла; и this для ссылки. Эти компоненты были локомотивами разработки React на протяжении всей его истории.

Удачно названные функциональные компоненты - это функции, возвращающие JSX. Вот типичный пример:

const FunctionalExample = props => {
  const greeting = 'Howdy'
  return (
    <span>
      {greeting}, {props.name}!
    </span>
  )
}

Функциональные компоненты не имеют внутреннего состояния, методов и this. Чтобы получить доступ к переданным реквизитам, мы обращаемся к параметру props.

Поскольку они содержат меньше внутренней логики, функциональные компоненты иногда называют «тупыми» или «презентационными» компонентами.

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

return (
  <section>
    <ClassExample name="Sean" />
    <FunctionalExample name="partner" />
  </section>
)

С точки зрения родителей, эти два типа компонентов идентичны. Чтобы понять, как React внутренне понимает и обрабатывает эти два разных типа компонентов, я рекомендую книгу Дэна Абрамова Как React отличает класс от функции?.

Это упрощенные определения и примеры, но они послужат нам хорошей основой для остальной части статьи.

Компонент класса

У меня есть компонент, который делает запрос API при монтировании, сохраняет ответ в состоянии, а затем отображает некоторый JSX на основе данных. Вот код:

class ClassComponent extends React.Component {
  state = {
    url: null
  }
  componentDidMount() {
    fetch('https://random.dog/woof.json')
      .then(raw => raw.json())
      .then(res => this.setState(res))
  }
  render() {
    return (
      <section>
        <h2>Doggo!</h2>
        {this.state.url ? (
          <img src={this.state.url} alt="A cute dog" />
        ) : (
          <p>Fetching</p>
        )}
      </section>
    )
  }
}

Примечание. Чтобы создать этот пример, я просмотрел замечательный список Общедоступных API Тодда Мотто. Ознакомьтесь с ним и поставьте ему звезду, если сочтете его полезным.

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

Функциональный компонент

Используя React Hooks, мы можем создать тот же пример сверху, используя функциональный компонент.

Настраивать

Сначала мы создадим новый файл для нашего функционального кода и настроим базовую функцию. Затем скопируйте return из нашего компонента класса и удалите все ссылки на this.state. Нам нужно объявить переменную, чтобы предотвратить ошибки ссылок, поэтому назовем ее url.

const FunctionalComponent = props => {
  const url // We'll change this later
  return (
    <section>
      <h2>Doggo!</h2>
      {url ? (
        <img src={url} alt="A cute dog" />
      ) : (
        <p>Fetching</p>
      )}
    </section>
  )
}

useEffect

В нашем компоненте класса мы делаем запрос AJAX, когда компонент монтируется и сохраняет эти данные в состоянии. componentDidMount и другие методы жизненного цикла ограничены классами. Однако команда React смогла реализовать эти функции с помощью хука useEffect.

useEffect - это метод, который принимает два параметра: 1) функцию, вызываемую при монтировании, и 2) переменные, которые необходимо отслеживать для обновления. В нашем примере нам понадобится только первый из этих двух параметров.

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

const FunctionalComponent = props => {
  const url // We'll change this later
  React.useEffect(() => {
    fetch('https://random.dog/woof.json')
      .then(raw => raw.json())
      .then(res => console.log(res))
  }, [])
  return (
    <section>
      <h2>Doggo!</h2>
      {url ? (
        <img src={url} alt="A cute dog" />
      ) : (
        <p>Fetching</p>
      )}
    </section>
  )
}

Если вы пишете код, вы должны увидеть в консоли что-то вроде следующего, когда компонент монтируется:

{ "url": "https://random.dog/867580b3d005.jpg" }

Это показывает нам, что наш вызов API работает, но регистрация ответа не является нашей целью; нам нужно сохранить данные на потом. Вернемся к созданной нами переменной url.

useState

Теперь, когда у нас есть ответ от нашего API, нам нужно иметь возможность фиксировать это значение и ссылаться на него. В нашем компоненте класса мы использовали его состояние и метод setState для запоминания этих данных.

Хук useState в React дает нам доступ к аналогичному API без использования класса. При вызове функции и, при необходимости, передаче значения по умолчанию, мы получаем две возвращаемые переменные: 1) ссылку на значение и 2) функцию для обновления этого значения соответственно.

Используя деструктуризацию массива, мы можем захватить эти переменные и присвоить им имя в соответствии с соглашением: x и setX. В нашем примере мы вызовем значение url и функцию установки setUrl.

Мы заменим наш старый url результатами из useState:

const FunctionalComponent = props => {
  const [url, setUrl] = React.useState(null)
  React.useEffect(() => {
    fetch('https://random.dog/woof.json')
      .then(raw => raw.json())
      .then(res => console.log(res))
  }, [])
  return (
    <section>
      <h2>Doggo!</h2>
      {url ? (
        <img src={url} alt="A cute dog" />
      ) : (
        <p>Fetching</p>
      )}
    </section>
  )
}

Используя useState, мы можем быстро добавить базовое состояние к нашему функциональному компоненту.

В приведенном выше коде мы просто регистрируем ответ от нашего API. Теперь мы хотим использовать функцию setUrl, чтобы установить значение или URL-адрес в состоянии нашего компонента.

const FunctionalComponent = props => {
  const [url, setUrl] = React.useState(null)
  React.useEffect(() => {
    fetch('https://random.dog/woof.json')
      .then(raw => raw.json())
      .then(res => setUrl(res.url))
  }, [])
  return (
    <section>
      <h2>Doggo!</h2>
      {url ? (
        <img src={url} alt="A cute dog" />
      ) : (
        <p>Fetching</p>
      )}
    </section>
  )
}

Теперь давайте вернемся ко второму параметру, переданному в useEffect. Помните, что он содержит список переменных, которые заставят эффект запускаться снова при изменении.

Наш эффект зависит от одной внешней переменной: setUrl. Эта функция генерируется один раз useState, и добавление ее в массив гарантирует, что наш вызов fetch не будет запускаться повторно. (Совет от Хун Тран Вану за это исправление)

const FunctionalComponent = props => {
  const [url, setUrl] = React.useState(null)
  React.useEffect(() => {
    fetch('https://random.dog/woof.json')
      .then(raw => raw.json())
      .then(res => setUrl(res.url))
  }, [setUrl])
  return (
    <section>
      <h2>Doggo!</h2>
      {url ? <img src={url} alt="A cute dog" /> : <p>Fetching</p>}
    </section>
  )
}

Вот и все! Ознакомьтесь с живым примером на CodeSandbox, чтобы увидеть два компонента бок о бок:

Https://codesandbox.io/s/wwvmq1v407

Сравнение

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

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

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

Примите бесклассовое будущее

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

Но не паникуйте и конвертируйте все компоненты вашего класса прямо сейчас; « Их будут поддерживать очень долго ». Однако, когда у вас есть возможность реорганизовать компонент класса в будущем, подумайте о преобразовании его в функциональный компонент с помощью хуков.

Надеюсь, это поможет! Дайте мне знать, если у вас есть какие-либо вопросы, комментарии или исправления в Twitter: @_seanmcp.

Удачного кодирования!

Первоначально опубликовано на www.seanmcp.com.