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

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

Перед реализацией крючка

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

Допустим, у нас есть компонент, и мы хотим отобразить другой компонент в зависимости от ширины окна браузера.

Например,

  • Если ширина окна 800px или больше, то мы хотим отрендерить Desktop компонент.
  • Если ширина окна меньше 800px, мы хотим отобразить компонент Mobile.

Вот простое решение:

import React, { useEffect, useState } from 'react'
const MyComponent: FC = () => {
  const [width, setWidth] = useState(window.innerWidth)
  
  const handleResize = () => setWidth(window.innerWidth)
  useEffect(() => {
    window.addEventListener('resize', handleResize)
    return () => window.removeEventListener('resize', handleResize)
  }, [])
  return width < 800 ? <Mobile /> : <Desktop />
}

Давайте разберем этот код, ладно?

В этом компоненте мы сохраняем ширину окна в переменной width, хранящейся в переменной React setState.

Это значение setState width устанавливается с помощью обратного вызова setWidth, который вызывается функцией handleResize.

Мы используем useEffect, чтобы добавить прослушиватель событий к window, который вызывает handleResize всякий раз, когда изменяется размер окна. Этот прослушиватель событий добавляется при монтировании компонента. Когда компонент отключен, мы очищаем его, удаляя прослушиватель событий.

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

Это довольно простой пример, но он сделан для того, чтобы мы могли сосредоточиться на том, что важно, и отфильтровать нерелевантную информацию.

Что плохого в этом подходе?

Ничего такого…

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

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

Было бы неплохо, если бы у нас было какое-нибудь простое в использовании многоразовое решение, которое решает эти проблемы?

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

Решение

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

После кодирования решения у нас будет useViewport обработчик, который мы разработали в другом файле, который будет работать следующим образом:

import React, { FC } from 'react'
import { useViewport } from './use-viewport'
const MyComponent: FC = () => {
  // See everything simplified to this one line.
  const { width } = useViewport()
  return width < 800 ? <Mobile /> : <Desktop />
}

Так намного проще, правда? Теперь давайте создадим наш useViewport хук и реализуем его!

Чтобы предотвратить подписку на несколько слушателей событий, мы собираемся использовать React Context API.

Вот полная реализация файла use-viewport:

import React, {
  createContext,
  FC,
  useContext,
  useEffect,
  useState,
} from 'react'
interface IViewport {
  width: number
}
const ViewportContext = createContext<IViewport>({
  width: window.innerWidth,
})
export const ViewportProvider: FC = ({ children }) => {
  const [width, setWidth] = useState(window.innerWidth)
  const handleResize = () => setWidth(window.innerWidth)
  useEffect(() => {
    window.addEventListener('resize', handleResize)
    return () => window.removeEventListener('resize', handleResize)
  }, [])
  return (
    <ViewportContext.Provider value={{ width }}>
      {children}
    </ViewportContext.Provider>
  )
}
export function useViewport() {
  return useContext<IViewport>(ViewportContext)
}

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

Затем мы создаем пользовательский хук useViewport, чтобы легко получить доступ к значению ширины в наших компонентах. Если вы не знакомы с API контекста React, я настоятельно рекомендую прочитать официальную документацию React, чтобы лучше понять ее. Хотя лично я больше всего узнал о Context API, используя их при создании пользовательских хуков.

При чтении приведенного выше кода наиболее важные части, которые следует вынести, следующие:

  • ViewportContext создается с использованием функции React createContext.
  • ViewportContext содержит только значение width, так что его может использовать созданный нами useViewport хук. Все остальное не важно для нашей реализации ловушки.
  • Значение width обновляется за кулисами в компоненте ViewportProvider.

Заключительные шаги

Эта часть очень важна! Наш useViewport хук пока не может быть использован. Нам нужно реализовать компонент ViewportProvider в нашем приложении, чтобы реализация ловушки действительно имела доступ к контексту. Помните: контексту React, как и Redux, нужен провайдер.

Для этого я рекомендую реализовать это рядом с корнем вашего React App.

Что-то вроде этого должно хватить:

import React from 'react' 
import ReactDOM from 'react-dom'
import App from './app'
import ViewportProvider from './use-viewport'
ReactDOM.render(
  <ViewportProvider>
    <App />
  </ViewportProvider>,
  document.getElementById('root')
)

Вот и все! Теперь вы сможете легко использовать хук useViewport в своем приложении 🙂

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

PS: Я назвал это useViewport вместо useWindowWidth, потому что я расширил функциональность ловушки. Вскоре я напишу статью о Части 2, чтобы включить некоторые удобные улучшения в крючок, который поможет сделать его еще более удобным в использовании.

Первоначально опубликовано на https://www.barrymichaeldoyle.com 13 апреля 2020 г.