существуетЗачем 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.theme
properties, но когда другие свойства 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
Плагины для помощи в разработке, потому что они действительно могут помочь нам найти проблемы в коде во время нашей разработки, но иногда попытки избавиться от его предупреждений действительно раздражают :).
В следующей статье этой серии я научу вас тестировать наш пользовательский хук, чтобы улучшить качество нашего кода, так что следите за обновлениями.
использованная литература
- When to useMemo and useCallback
- Preventing rerenders with React.memo and useContext hook
- React Hook Reference
- useReducer vs useState in React
Тенденции в области персональных технологий
Статья взята из моеголичный блог
Добро пожаловать в публичный аккаунтатакующий лукучиться и расти вместе