Сверхдетальное практическое руководство по React Hook

React.js
Сверхдетальное практическое руководство по React Hook

существуетЗачем React нужны хукиВ этой статье мы обсудили, почему команда разработчиков React добавила хуки в функциональный компонент.В этой статье я предоставлю вам более полное практическое руководство по React Hook, включая следующие аспекты:

  • Что такое Реакт Хук
  • Знакомство с общими хуками
    • useState
    • useEffect
    • useRef
    • useCallback
    • useMemo
    • useContext
    • useReducer
  • Пользовательский крючок

Что такое Реакт Хук

React Hook — это новое свойство, добавленное после React 16.8, проще говоря,React Hook — это некоторые встроенные функции, предоставляемые React, которые позволяют компонентам функций и компонентов класса иметь состояние компонента и выполнять побочные эффекты..

Знакомство с общими хуками

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

  • эффект
  • Применение
  • Меры предосторожности

useState

эффект

useStateЭто очень просто понять, и компонент классаthis.stateто же, используется дляУправление состоянием компонента. До появления React Hook функциональный компонент также назывался функциональным компонентом без состояния (FSC), потому что функциональный компонент генерирует новую область действия функции каждый раз, когда он выполняется, поэтому невозможно визуализировать один и тот же компонент между разными рендерингами. общий, поэтому, как только разработчику нужно ввести состояние в компонент, ему нужно изменить исходный функциональный компонент на компонент класса, что очень усложняет работу разработчика.useStateзаключается в том, чтобы решить эту проблему,Это позволяет функциональному компоненту сохранять свое состояние в месте (ячейке памяти) в среде выполнения React (ячейка памяти), чтобы состояние можно было извлекать из этого места каждый раз, когда компонент повторно отображается, и когда состояние извлекается. При обновлении компонент также будет повторно отображать.

Применение

const [state, setState] = useState(initialState)

useStateполучитьinitialStateПеременная используется как начальное значение состояния, а возвращаемое значение представляет собой массив. Первый элемент возвращаемого массива представляет текущийstateПоследнее значение , второй элемент является обновлениемstateФункция. Здесь следует отметить, чтоstateа такжеsetStateИмена этих двух переменных не фиксированы, вы должны выбрать разные имена в соответствии с реальной ситуацией в вашем бизнесе, которая может бытьtextа такжеsetText,так же может бытьwidthа такжеsetWidthНоменклатура этого типа. (Студенты, которые не знакомы с приведенным выше заданием по деструктуризации массива, могут прочитать его.MDNвведение).

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

import React, { useState } from 'react'
import ReactDOM from 'react-dom'

const App = () => {
  const [counter, setCounter] = useState(0)
  const [text, setText] = useState('')

  const handleTextChange = (event) => {
    setText(event.target.value)
  }

  return (
    <>
      <div>Current counter: {counter}</div>
      <button
        onClick={() => setCounter(counter + 1)}
      >
        Increase counter
      </button>
      <input
        onChange={handleTextChange}
        value={text}
      />
    </>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

и компонент классаthis.setStateAPI похоже,setCounterа такжеsetTextможет получить функцию в качестве параметра, эта функция называетсяupdater,updaterПолученный параметр - текущее состояниепоследнее значение, возвращаемое значение равноследующее состояние. Например, параметр setCounter можно изменить на функцию:

<button
  onClick={() => {
    setCounter(counter => counter + 1)
  }}
>
  Increase counter
</button>

useStateизinitialStateЭто также может быть функция, используемая для генерации начального значения состояния.Этот метод в основном позволяет избежать каждый раз, когда компонент отображается.initialStateнужно пересчитать. Вот простой пример:

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props)
  return initialState
})

Меры предосторожности

setState является полной заменой

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

const [state, setState] = useState({ left: 0, top: 0, width: 0, height: 0 })

В приведенном выше коде, поскольку мы будем отделять информацию о местоположении DOM{left: 0, top: 0}и информация о размере{width: 0, height: 0}привязан к тому жеstate, поэтому нам нужно поддерживать другое состояние при обновлении любого состояния:

const handleContainerResize = ({ width, height }) => {
  setState({...state, width, height})
}

const handleContainerMove = ({ left, top }) => {
  setState({...state, left, top})
}

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

// separate state into position and size states
const [position, setPosition] = useState({ left: 0, top: 0 })
const [size, setSize] = useState({ width: 0, height: 0})

const handleContainerResize = ({ width, height }) => {
  setSize({width, height})
}

const handleContainerMove = ({ left, top }) => {
  setPosition({left, top})
}

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

При установке одного и того же значения состояния setStatebailing out of update

Если setState получает新的stateа также当前的stateтот же (метод оценкиObject.is), React не будет повторно отображать дочерние компоненты или запускатьside effect. Здесь следует отметить, что, хотя React не будет отображать дочерние компоненты, он все равно будет повторно отображать текущий компонент.useMemoдля оптимизации производительности.

setState не имеет функции обратного вызова

Будь тоuseStateИли компонент классаthis.setStateобеАсинхронный вызов, то есть каждый раз, когда компоненты вызывают их, они не могут получить последнее значение состояния. Чтобы решить эту проблему, компонент классаthis.setStateПозволяет вам получить последнее значение состояния через функцию обратного вызова, использование выглядит следующим образом:

this.setState(newState, state => {
  console.log("I get new state", state)
})

Функция setState функционального компонента не имеет такой функции обратного вызова, которая может получить последнее состояние, но мы можем использоватьuseEffectЧтобы добиться того же эффекта, вы можете обратиться к этому StackOverflowОбсуждать.

useEffect

эффект

useEffectОн используется для того, чтобы функциональный компонент также имел побочные эффекты. Итак, каковы побочные эффекты? Начнем с определения из Википедии:

In computer science, an operation, function or expression is said to have a side effect if it modifies some state variable value(s) outside its local environment, that is to say has an observable effect besides returning a value (the main effect) to the invoker of the operation.

С точки зрения непрофессионала,Побочные эффекты функции — это другие эффекты, которые функция оказывает на внешнюю среду в дополнение к возвращаемому значению.. Например, если каждый раз, когда мы выполняем функцию, функция будет работать с глобальной переменной, то операция с глобальной переменной является побочным эффектом этой функции. В мире React наши побочные эффекты можно условно разделить на две категории.Вызов API браузера, например, используяaddEventListenerЧтобы добавить функции прослушивания событий и т. д., используйте другой типИнициировать запрос на получение данных сервера, такие как асинхронное получение информации о пользователе при монтировании карты пользователя. Прежде чем выйдет хук, если нам нужно выполнить побочные эффекты в компоненте, нам нужно написать компонент как компонент класса, а затем написать побочный эффект в функции жизненного цикла компонента, что на самом деле вызовет много кода. проблемы с дизайном.Подробности вы можете найти в моей предыдущей статьеЗачем React нужны хуки. После выхода Hook разработчики смогут использовать его в функциональном компоненте.useEffectдля определения побочных эффектов. несмотря на то чтоuseEffectв основном покрытиеcomponentDidMount,componentDidUpdate,componentWillUnmountВсе сценарии, в которых функции жизненного цикла используются в комбинации, ноuseEffectСуществует еще существенное отличие от концепции проектирования функций жизненного цикла.Если вы слепо используете образ мышления функций жизненного цикла, чтобы понять и использоватьuseEffectЭто может вызвать некоторые странные проблемы.Если вам интересно, вы можете прочитать эту статью, написанную разработчиком ядра React Дэном:A Complete Guide to useEffect, в котором описывается использованиеuseEffectБолее правильный образ мышления (ментальная модель).

Применение

useEffect(effect, dependencies?)

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

import React, { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'

const UserDetail = ({ userId }) => {
  const [userDetail, setUserDetail] = useState({})

  useEffect(() => {
    fetch(`https://myapi/users/${userId}`)
      .then(response => response.json())
      .then(user => setUserDetail(userDetail))
  })

  return (
    <div>
      <div>User Name: {userDetail.name}</div>
    </div>
  )
}

ReactDOM.render(<UserDetail />, document.getElementById('root'))

Побочным эффектом получения сведений о пользователе, как определено выше, будетUserDetail组件каждый разПосле рендерингаВыполняется, поэтому, когда компонент монтируется в первый раз, он инициирует запрос на сервер для получения сведений о пользователе, а затем обновляетuserDetailЗначение , первое монтирование здесь можно сравнить с Class ComponentcomponentDidMount. Но если вы попытаетесь запустить приведенный выше код, то обнаружите, что код входит в бесконечный цикл: компонент будет продолжать делать запросы к серверу. Причина этого бесконечного циклаuseEffectвнутри вызоваsetUserDetail, эта функция обновитuserDetailзначение, вызывающее повторный рендеринг компонента, а после повторного рендерингаuseEffectизeffectПродолжайте выполняться, и компонент снова перерисовывается. . . Чтобы избежать повторяющихся побочных эффектов,useEffectпозволяет нам передать второй параметрdependenciesограничить время выполнения побочного эффекта: указаноdependenciesпобочные эффекты,только вdependenciesВыполняется при изменении значения элемента массива, поэтому, если мы хотим избежать входа приведенного выше кода в бесконечный цикл, нам нужно поставитьuserIdуказаны для наших определенных побочных эффектовdependencies:

import React, { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'

const UserDetail = ({ userId }) => {
  const [userDetail, setUserDetail] = useState({})

  useEffect(() => {
    fetch(`https://myapi/users/${userId}`)
      .then(response => response.json())
      .then(user => setUserDetail(userDetail))
  }, [userId])

  return (
    <div>
      <div>User Name: ${userDetail.name}</div>
    </div>
  )
}

ReactDOM.render(<UserDetail />, document.getElementById('root'))

В дополнение к инициированию запросов на стороне сервера нам часто нужноuseEffectВызовите API браузера внутри, например, используяaddEventListenerдобавить функции прослушивания событий браузера и т. д. Как только мы используемaddEventListenerнужно звонить в нужное времяremoveEventListenerубрать обработчик события, иначе будут проблемы с производительностью,useEffectпозволяет нам вернутьcleanupфункция, эта функция будет в компонентеперед повторным рендерингомПосле выполнения мы можем удалить прослушиватель события в возвращаемой функции. Ниже приведен конкретный пример:

import React, { useEffect } from 'react'
import ReactDOM from 'react-dom'

const WindowScrollListener = () => {
  useEffect(() => {
    const handleWindowScroll = () => console.log('yean, window is scrolling!')
    window.addEventListener('scroll', handleWindowScroll)

    // this is clean up function
    return () => {
      window.removeEventListener(handleWindowScroll)
    }
  }, [])

  return (
    <div>
      I can listen to the window scroll event!
    </div>
  )
}

ReactDOM.render(<WindowScrollListener />, document.getElementById('root'))

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

Меры предосторожности

Избегайте «старых» переменных

мы на самом деле используемuseEffectСамая распространенная проблема, с которой можно столкнуться в процессе, заключается в том, что при вызове нашей функции эффекта некоторые состояния, реквизиты или полученные переменные недо настоящего временипеременная, но передСтарыйПеременная. Причина этой проблемы заключается в следующем: определяемый нами побочный эффект на самом деле является функцией, а область действия JS — это лексическая область видимости, поэтому значение переменной, используемой функцией, является значением переменной, используемой функцией.когда определеноЧтобы быть уверенным, говоря простыми словами, эффект useEffect будетзапомнитьЗначение внешней переменной, когда она была определена, поэтому ее можно вызвать со значением, отличным отдо настоящего времениценность . Есть два способа решить эту проблему, один — сохранять те переменные, которые вы хотите получать последними значениями каждый раз, когда эффект вызывается в ref, и обновлять значение ref каждый раз, когда компонент рендерится:

const [someState, setSomeState] = useState()
const someStateRef = useRef()

someStateRef.current = someState

useEffect(() => {
  ...
  const latestSomeState = someStateRef.current
  console.log(latestSomeState)
}, [otherDependencies...])

Такой подход не очень элегантный, но он решает нашу проблему, если вы не понимаетеuseRefДля использования вы можете проверить эту статьюuseRefэта часть. Еще один способ решить эту проблему — добавить побочные эффекты.использоватьВсе переменные добавляются к эффектуdependenciesЭто также рекомендуемый подход. В реальной разработке мы можем использовать собственныеeslint-plugin-react-hooksизexhaustive-depsПравила реализации ограничений кодирования, после добавления этого ограничения в ваш проект eslint сообщит вам поставить someState на этапе разработки кодаuseEffectизdependenciesтак что вы не можете использоватьuseRefдля хранения значения someState, например следующий код:

const [someState, setSomeState] = useState()

useEffect(() => {
  ...
  console.log(someState)
}, [otherDependencies..., someState])

useRef

эффект

useRefОн используется для обмена некоторыми данными между различными визуализациями компонентов.Его функция такая же, как и в компоненте класса.thisНазначение такое же.

Применение

const refObject = useRef(initialValue)

useRefперениматьinitialValueВ качестве начального значения его возвращаемое значение является однимrefобъект, этот объект.currentСвойство — это последнее значение для этих данных. использоватьuseRefОдним из самых простых случаев является сохранение ссылки на объект DOM в функциональном компоненте, как в следующем примере:

import { useRef, useEffect } from 'react'
import ReactDOM from 'react-dom'

const AutoFocusInput = () => {
  const inputRef = useRef(null)

  useEffect(() => {
    // auto focus when component mount
    inputRef.current.focus()
  }, [])

  return (
    <input ref={inputRef} type='text' />
  )
}

ReactDOM.render(<AutoFocusInput />, document.getElementById('root'))

В приведенном выше коде inputRef на самом деле является{current: inputDomInstance}объект, но это гарантирует, что один и тот же объект будет получен каждый раз, когда компонент визуализируется.

Меры предосторожности

Обновление объекта REF не запускает компонент Re-Render

useRefВозвращенный Ref Object не вызывает компонентов при повторном использовании.перерисовать, если у вас есть это требование, используйтеuseStateдля хранения данных.

useCallback

эффект

С появлением Hook разработчики стали все больше и больше использовать функциональный компонент для разработки требований. Когда разработчики определяют функциональный компонент, им часто нужно определить некоторые встроенные функции в теле функции.Эти встроенные функции будут переопределяться каждый раз при повторном рендеринге компонента, если они передаются дочерним компонентам в качестве реквизита. компонент для повторного рендеринга, даже если значение других свойств не изменилось, и бесполезный повторный рендеринг компонента может вызвать некоторые проблемы с производительностью. Другая проблема с регенерацией новой встроенной функции каждый раз заключается в том, что мы используем встроенную функцию какdependencyпройти вuseEffectизdependenciesМассивы, потому что функция часто регенерируется, поэтомуuseEffectЭффект внутри будет вызываться часто. Чтобы решить вышеуказанную проблему, React позволяет нам использоватьuseCallbackПриходитьзапомнить(запоминать) текущую определенную функцию и возвращать ранее определенную функцию вместо использования вновь определенной функции при следующем отображении компонента.

Применение

const memoizedCallback = useCallback(callback, dependencies)

useCallbackПолучает два параметра, первый параметр — это функция, которую нужно запомнить, а второй параметр — это значение функции.dependencies,ТолькоdependenciesКогда значение элемента в массиве изменяетсяuseCallbackвернет вновь определенную функцию, иначеuseCallbackвернет ранее определенную функцию. Вот простое использованиеuseCallbackЧтобы оптимизировать пример, в котором дочерние компоненты часто отображаются:

import React, { useCallback } from 'react'
import useSearch from 'hooks/useSearch'
import ReactDOM from 'react-dom'

// this list may contain thousands of items, so each re-render is expensive
const HugeList = ({ items, onClick }) => {
  return (
    <div>
      {
        items.map((item, index) => (
          <div
            key={index}
            onClick={() => onClick(index)}
          >
            {item}
          </div>
        ))
      }
    </div>
  )
}

const MemoizedHugeList = React.memo(HugeList)

const SearchApp = ({ searchText }) => {
  const handleClick = useCallback(item => {
    console.log('You clicked', item)
  }, [])
  const items = useSearch(searchText)

  return (
    <MemoizedHugeList
      items={items}
      onClick={handleClick}
    />
  )
}

ReactDOM.render(<SearchApp />, document.getElementById('root'))

В приведенном выше примере я определилHugeListКомпонент, так как этот компонент должен отображать большой список (элементов), каждый повторный рендеринг очень требователен к производительности, поэтому я используюReact.memoфункция, чтобы сделать компонент толькоonClickфункция иitemsОн отображается только при изменении массива.Если вы правыReact.memoЕсли вы не очень знакомы, вы можете прочитать, что я написалэта статья. Тогда яSearchAppвнутри использованиеMemoizedHugeList, так как я хочу избежать повторной отрисовки этого компонента, я используюuseCallbackзапомнить определениеhandleClick函数, чтобы при рендеринге за компонентомhandleClickВсе переменные указывают на одну и ту же функцию, поэтомуMemorizedHugeListПерерисовывается только при изменении элементов. Здесь следует отметить, что из-за моегоhandleClickФункция не использует никаких внешних зависимостей, поэтому ееdependenciesЭто пустой массив. Если ваша функция имеет внешние зависимости, не забудьте поместить зависимости вuseCallbackизdependenciesпараметры, иначе будут баги.

Меры предосторожности

Избегайте использования «старых» переменных в функциях

а такжеuseEffectАналогично, нам также нужно поместить всеuseCallbackВнешние переменные, используемые в обратном вызове, записываются вdependenciesвнутри массива, иначе мы могли бы оказаться вcallbackВызов использует значение «старой» внешней переменной.

Не все функции используют useCallback

Performance optimizations are not free. They ALWAYS come with a cost but do NOT always come with a benefit to offset that cost.

Любая оптимизация обходится дорого,useCallbackТо же самое справедливо. Когда мы вызываем функциональный компонентuseCallbackПри использовании функции React должен выполнить серию вычислений за кулисами, чтобы гарантировать, чтоdependenciesКогда ничего не меняется, мы получаем ту же функцию, поэтому, если мы злоупотребимuseCallbackЕсли это так, это не принесет воображаемой оптимизации производительности, но повлияет на нашу производительность.Например, следующий пример является плохим использованием.useCallbackпример:

import React, { useCallback } from 'react'
import ReactDOM from 'react-dom'

const DummyButton = () => {
  const handleClick = useCallback(() => {
    console.log('button is clicked')
  }, [])

  return (
    <button onClick={handleClick}>
      I'm super dummy
    </button>
  )
}

ReactDOM.render(<DummyButton />, document.getElementById('root'))

В приведенном выше примере используетсяuseCallbackНе играет никакой роли в оптимизации производительности кода, потому что приведенный выше код фактически эквивалентен следующему коду:

import React, { useCallback } from 'react'
import ReactDOM from 'react-dom'

const DummyButton = () => {
  const inlineClick = () => {
    console.log('button is clicked')
  }
  const handleClick = useCallback(inlineClick, [])

  return (
    <button onClick={handleClick}>
      I'm super dummy
    </button>
  )
}

ReactDOM.render(<DummyButton />, document.getElementById('root'))

Из приведенного выше кода видно, что даже если мы используемuseCallbackфункция, браузер выполняетDummyButtonЭта функция по-прежнему нуждается в создании новой встроенной функции.inlineClick, это и не использованиеuseCallbackЭффект тот же, и, кроме того, оптимизированный код также вызываетuseCallbackфункция, поэтому она на самом деле потребляет больше вычислительных ресурсов, чем раньше без оптимизации, и потому чтоuseCallbackНекоторые дополнительные переменные хранятся внутри функции (например, предыдущаяdependencies), поэтому он также будет потреблять больше ресурсов памяти. Поэтому мы не можем слепо использовать все встроенные функции.useCallbackдля переноса используйте только те функции, которые действительно нужно запомнитьuseCallback.

useMemo

эффект

useMemoа такжеuseCallbackочень похоже, за исключением того, что позволяет记住Любой тип переменной (не только функции).

Применение

const memoizedValue = useMemo(() => valueNeededToBeMemoized, dependencies)

useMemoПолучает функцию, возвращаемое значение которой является переменной, которую необходимо запомнить, когдаuseMemoвторой параметрdependenciesКогда значение элементов в массиве не изменилось,memoizedValueИспользуется последнее значение. Ниже приведен пример:

import React, { useMemo } from 'react'
import ReactDOM from 'react-dom'

const RenderPrimes = ({ iterations, multiplier }) => {
  const primes = React.useMemo(() => calculatePrimes(iterations, multiplier), [
    iterations,
    multiplier
  ])

  return (
    <div>
      Primes! {primes}
    </div>
  )
}

ReactDOM.render(<RenderPrimes />, document.getElementById('root'))

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

Меры предосторожности

Не все переменные должны быть заключены в useMemo.

а такжеuseCallbackТочно так же мы используем только те переменные, которые необходимо запомнить.useMemoЧтобы инкапсулировать, помните, чтобы не злоупотреблятьuseMemo, например, следующее является злоупотреблениемuseMemoпример:

import React, { useMemo } from 'react'
import ReactDOM from 'react-dom'

const DummyDisplay = () => {
  const items = useMemo(() => ['1', '2', '3'], [])
  
  return (
    <>
      {
        items.map(item => <div key={item}>{item}</div>)
      }
    </>
  )
}

ReactDOM.render(<DummyDisplay />, document.getElementById('root'))

В приведенном выше примере было бы лучше определить элементы непосредственно вне компонента:

import React from 'react'
import ReactDOM from 'react-dom'

const items = ['1', '2', '3']

const DummyDisplay = () => {  
  return (
    <>
      {
        items.map(item => <div key={item}>{item}</div>)
      }
    </>
  )
}

ReactDOM.render(<DummyDisplay />, document.getElementById('root'))

useContext

эффект

Мы знаем, что способ передачи параметров между компонентами в React — это props.Если мы определяем какие-то состояния в родительском компоненте, и эти состояния нужно использовать в глубоко вложенных подкомпонентах компонента, нам нужно поставить эти состояния. слоем в виде реквизита, что вызываетprops drillingЭта проблема. Чтобы решить эту проблему, React позволяет нам использоватьContextдля передачи состояния между родительским компонентом и любым уровнем дочерних компонентов ниже него. В функциональном компоненте мы можем использоватьuseContextКрючок для использованияcontext.

Применение

const value = useContext(MyContext)

useContextполучитьcontextобъект в качестве параметра,contextобъект сделанReact.createContextсгенерированная функция.useContextВозвращаемое значение является текущимcontextзначение, которое определяется ближайшим соседом<MyContext.Provider>решать. После использования в компонентеuseContextЭто эквивалентно компоненту, подписанному на этотcontextизменяется, когда самый последний<MyContext.Provider>изcontextПри изменении значения использованиеcontextдочерние компоненты будут инициированы для повторного рендеринга, и они получатcontextпоследнее значение . Вот конкретный пример:

import React, { useContext, useState } from 'react'
import ReactDOM from 'react-dom'

// define context
const NumberContext = React.createContext()

const NumberDisplay = () => {
  const [currentNumber, setCurrentNumber] = useContext(NumberContext)

  const handleCurrentNumberChange = () => {
    setCurrentNumber(Math.floor(Math.random() * 100))
  }

  return (
    <>
      <div>Current number is: {currentNumber}</div>
      <button onClick={handleCurrentNumberChange}>Change current number</button>
    </>
  )
}

const ParentComponent = () => {
  const [currentNumber, setCurrentNumber] = useState({})

  return (
    <NumberContext.Provider value={[currentNumber, setCurrentNumber]}>
      <NumberDisplay />
    </NumberContext.Provider>
  )
}

ReactDOM.render(<ParentComponent />, document.getElementById('root'))

Меры предосторожности

Избегайте бесполезного рендеринга

Мы упоминали выше, что если функциональный компонент используетuseContext(SomeContext)то он подписывается на этоSomeContextизменения, так что когдаSomeContext.ProviderизvalueКогда происходит изменение, компонент будет повторно визуализирован. Одна проблема заключается в том, что мы можем поместить много разных данных в один и тот же файл.contextвнутри, в то время как разные дочерние компоненты могут заботиться только об этомcontextчасть данных, когдаcontextКогда какое-либо значение в нем изменяется, они будут повторно отображаться независимо от того, используют ли эти компоненты данные или нет, что может вызвать некоторые проблемы с производительностью. Вот простой пример:

import React, { useContext, useState } from 'react'
import ExpensiveTree from 'somewhere/ExpensiveTree'
import ReactDOM from 'react-dom'

const AppContext = React.createContext()

const ChildrenComponent = () => {
  const [appContext] = useContext(AppContext)
  const theme = appContext.theme

  return (
    <div>
      <ExpensiveTree theme={theme} />
    </div>
  )
}

const App = () => {
  const [appContext, setAppContext] = useState({ theme: { color: 'red' }, configuration: { showTips: false }})

  return (
    <AppContext.Provider value={[appContext, setAppContext]}>
      <ChildrenComponent />
    </AppContext.Provider>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

В приведенном выше примере ChildrenComponent использует только appContext.themeproperties, но когда другие свойства appContext, такие как конфигурация, обновляются, ChildrenComponent также будет повторно отображаться, а ChildrenComponent вызывает очень требовательный к производительности компонент ExpensiveTree, поэтому эти бесполезные рендеринги повлияют на производительность нашей страницы, решить вышеуказанную проблему. Есть три метода:

Разделить контекст

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

import React, { useContext, useState } from 'react'
import ExpensiveTree from 'somewhere/ExpensiveTree'
import ReactDOM from 'react-dom'

const ThemeContext = React.createContext()
const ConfigurationContext = React.createContext()

const ChildrenComponent = () => {
  const [themeContext] = useContext(ThemeContext)

  return (
    <div>
      <ExpensiveTree theme={themeContext} />
    </div>
  )
}

const App = () => {
  const [themeContext, setThemeContext] = useState({ color: 'red' })
  const [configurationContext, setConfigurationContext] = useState({ showTips: false })

  return (
    <ThemeContext.Provider value={[themeContext, setThemeContext]}>
      <ConfigurationContext.Provider value={[configurationContext, setConfigurationContext]}>
        <ChildrenComponent />
      </ConfigurationContext.Provider>
    </ThemeContext.Provider>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))
Разделите свои компоненты и используйте заметки для оптимизации компонентов, потребляющих производительность.

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

import React, { useContext, useState } from 'react'
import ExpensiveTree from 'somewhere/ExpensiveTree'
import ReactDOM from 'react-dom'

const AppContext = React.createContext()

const ExpensiveComponentWrapper = React.memo(({ theme }) => {
  return (
    <ExpensiveTree theme={theme} />
  )
})

const ChildrenComponent = () => {
  const [appContext] = useContext(AppContext)
  const theme = appContext.theme

  return (
    <div>
      <ExpensiveComponentWrapper theme={theme} />
    </div>
  )
}

const App = () => {
  const [appContext, setAppContext] = useState({ theme: { color: 'red' }, configuration: { showTips: false }})

  return (
    <AppContext.Provider value={[appContext, setAppContext]}>
      <ChildrenComponent />
    </AppContext.Provider>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))
Без разделения компонентов вы также можете использовать useMemo для оптимизации

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

import React, { useContext, useState, useMemo } from 'react'
import ExpensiveTree from 'somewhere/ExpensiveTree'
import ReactDOM from 'react-dom'

const AppContext = React.createContext()

const ChildrenComponent = () => {
  const [appContext] = useContext(AppContext)
  const theme = appContext.theme

  return useMemo(() => (
      <div>
        <ExpensiveTree theme={theme} />
      </div>
    ),
    [theme]
  )
}

const App = () => {
  const [appContext, setAppContext] = useState({ theme: { color: 'red' }, configuration: { showTips: false }})

  return (
    <AppContext.Provider value={[appContext, setAppContext]}>
      <ChildrenComponent />
    </AppContext.Provider>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

useReducer

эффект

useReducerПроще говоря, это позволяет нам использовать в функциональном компоненте, напримерreduxпройти то же самоеreducerа такжеactionдля управления переходами состояний наших компонентов.

Применение

const [state, dispatch] = useReducer(reducer, initialArg, init?)

useReducerа такжеuseStateТочно так же все они используются для управления состоянием компонентов, но сuseStateизsetStateРазница в том,useReducerвернутьdispatchФункции используются для запуска определенных измененийstateизactionвместо того, чтобы установить его напрямуюstateзначение, как для различныхactionКак сгенерировать новое значение состояния находится вreducerопределяется внутри.useReducerПолучены три параметра:

  • редуктор: это функция, сигнатура которой(currentState, action) => newState, по сигнатуре его функции видно, что она получит текущее состояние и текущийdispatchизactionКак параметр, а затем возврат к следующему состоянию, то есть он отвечает за переход состояния (state transition).
  • initialArg: если вызывающая сторона не предоставила третийinitпараметр, этот параметр представляет этоreducerисходное состояние, еслиinitЕсли параметр указан,initialArgбудет передан в качестве параметраinitфункция для создания начального состояния.
  • init: это функция, используемая для генерации начального состояния, сигнатура ее функции(initialArg) => initialState, как видно из его сигнатуры функции, которую он получаетuseReducerвторой параметрinitialArgв качестве параметра и сгенерировать начальное состояниеinitialState. НижеuseReducerПростой пример:
import React, { useState, useReducer } from 'react'

let todoId = 1

const reducer = (currentState, action) => {
  switch(action.type) {
    case 'add':
      return [...currentState, {id: todoId++, text: action.text}]
    case 'delete':
      return currentState.filter(({ id }) => action.id !== id)
    default:
      throw new Error('Unsupported action type')
  }
}

const Todo = ({ id, text, onDelete }) => {
  return (
    <div>
      {text}
      <button
        onClick={() => onDelete(id)}
      >
        remove
      </button>
    </div>
  )
}

const App = () => {
  const [todos, dispatch] = useReducer(reducer, [])
  const [text, setText] = useState('')

  return (
    <>
      {
        todos.map(({ id, text }) => {
          return (
            <Todo
              text={text}
              key={id}
              id={id}
              onDelete={id => {
                dispatch({ type: 'delete', id })
              }}
            />
          )
        })
      }
      <input onChange={event => setText(event.target.value)} />
      <button
        onClick={() => {
          dispatch({ type: 'add', text })
          setText('')
        }}
      >
        add todo
      </button>
    </>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

Меры предосторожности

useReducer vs useState

useReducerа такжеuseStateМожет использоваться для управления состоянием компонентов, самая большая разница между ними заключается в том,useReducerЕдиное управление состоянием и изменениями состояния вreducerФункция внутри, поэтому для какого-то сложного управления состоянием нам будет очень удобно отлаживать, т.к. ее статус изменен封闭的. И из-заuseStateвернутьsetStateЗначение нашего состояния можно установить прямо в любом месте, когда логика перехода состояния нашего компонента очень сложная, ее будет сложно отладить, т.к.开放的Государственное управление. В целом, вuseReducerа такжеuseStateПри выборе можно руководствоваться следующими принципами:

  • Используйте в следующих случаяхuseState
    • stateЗначения являются примитивами JS, такие какnumber, stringа такжеbooleanЖдать
    • stateЛогика преобразования очень проста
    • Различные состояния внутри компонента не связаны, они могут использовать несколько независимыхuseStateуправлять индивидуально
  • Используйте в следующих случаяхuseReducer
    • stateЗначениеobjectилиarray
    • stateЛогика преобразования очень сложна и должна использоватьсяreducerфункция единого управления
    • несколько в компонентеstateВзаимосвязаны, изменение одного состояния также требует изменения другого, поставить их в одно и то же состояние.stateИспользуйте редуктор для унифицированного управления
    • Состояние определяется в родительском компоненте, но вам нужно использовать и изменять состояние родительского компонента в глубоко вложенных дочерних компонентах, вы можете использовать обаuseReducerа такжеuseContextДва крючка, будетdispatchМетод помещается в контекст, чтобы избежатьprops drilling
    • Если вы хотите, чтобы управление состоянием было предсказуемым и удобным в сопровождении, пожалуйста,useReducer
    • Если вы хотите, чтобы ваши изменения состояния можно было проверить, используйтеuseReducer

Пользовательский крючок

Использование общих хуков, встроенных в React, было представлено выше, а затем давайте посмотрим, как написать свои собственные хуки.

эффект

Цель пользовательского хука — позволить некоторым нашим пакетам находиться между разными компонентами.Общая логика, не связанная с пользовательским интерфейсомЧтобы повысить эффективность разработки нашего бизнес-кода.

Что такое кастомный хук

Ранее мы говорили, что хук на самом деле является функцией, поэтому пользовательский хук также является функцией, но它在内部使用了React的内置Hook或者其它的自定义Hook. Хотя мы можем назвать наш пользовательский хук произвольно, другим разработчикам будет легче понять наш код и использовать некоторые инструменты разработки, такие какeslint-plugin-react-hooksЧтобы дать нам лучший намек, нам нужно поставить наш крючок наuseначните с случая верблюда, например.useLocation,useLocalStorageа такжеuseQueryStringи т.п.

пример

Вот пример простейшего пользовательского хука:

import React, { useState, useCallback } from 'react'
import ReactDOM from 'react-dom'

const useCounter = () => {
  const [counter, setCounter] = useState(0)
  
  const increase = useCallback(() => setCounter(counter => ++counter), [])
  const decrease = useCallback(() => setCounter(counter => --counter), [])

  return {
    counter,
    increase,
    decrease
  }
}

const App = () => {
  const { counter, increase, decrease } = useCounter()

  return (
    <>
      <div>Counter: {counter}</div>
      <button onClick={increase}>increase</button>
      <button onClick={decrease}>decrease</button>
    </>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

Суммировать

В этой статье я расскажу о некоторых часто используемых встроенных хуках в React и о том, как определить наши собственные хуки. В целом, React Hook — очень мощная функция. Рациональное ее использование может повысить скорость повторного использования нашего кода и эффективность разработки бизнес-кода. Однако у него также есть много скрытых ям, и каждый должен его использовать. Чтобы принять больше мер предосторожности, мое личное предложение состоит в том, что все пытаются использоватьeslint-plugin-react-hooksПлагины для помощи в разработке, потому что они действительно могут помочь нам найти проблемы в коде во время нашей разработки, но иногда попытки избавиться от его предупреждений действительно раздражают :).

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

использованная литература

Тенденции в области персональных технологий

Статья взята из моеголичный блог

Добро пожаловать в публичный аккаунтатакующий лукучиться и расти вместе