В этой серии будет описано, как использовать React Hooks, начиная с useState, и будет рассмотрено следующее:
Освоение API React Hooks поможет вам лучше использовать его в своей работе, а ваше владение React перейдет на новый уровень. В этой серии будет использоваться много примеров кода и демонстраций эффектов, которые очень просты в использовании для новичков и рецензентов.
На данный момент мы изучили 3 хука api,useState
, useEffect
, useContext
. Далее изучаем следующий хук api,useReducer
. Сначала поговорим о том, что такое редуктор и для чего он используется. Взгляните, что такое редюсер в JavaScript, это поможет понять, что такое хуки реакции.useReducer
. Хорошо, давайте начнем сейчас.
что такое useReducer
useReducer
это хук API для управления состоянием. даuseState
альтернатива.
ТакuseReducer
а такжеuseState
В чем разница? ответuseState
это использоватьuseReducer
построен.
Итак, когда использоватьuseReducer
ещеuseState
Шерстяная ткань? Мы можем найти ответ, завершив изучение этой главы.
reducer
useState - state
useEffect - side effects
useContext - context API
useReducer - reducers
Вы можете видеть, что useReducer также должен быть связан с редуктором, давайте посмотрим, что такое редюсер. Причина, по которой вам нужно знать, что такое редьюсер, заключается в том, что вам не нужно знать редукс, чтобы научиться использовать редьюсер.Конечно, если вы знаете редукс, вам будет легче понять эту главу. начать ниже
Если вы изучали родной JavaScript, вы обнаружите, что есть встроенные методы, такие какforeach
, map
, reduce
. Давайте посмотрим поближеreduce
метод. Доступно на MDNArray.prototype.reduce()
документация, в документации сказано
Метод reduce() выполняет функцию редуктора (выполняется в порядке возрастания), предоставленную вами для каждого элемента в массиве, суммируя ее результаты в одно возвращаемое значение.
const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;
// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// expected output: 10
// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));
// expected output: 15
Следует отметить, что метод сокращения принимает 2 параметра, первый — это функция редуктора, а второй — начальное значение (используемое функцией редуктора). Метод сокращения возвращает результат кумулятивной обработки функции.
Функция редуктора имеет 2 обязательных параметра:
-
accumulator
Аккумулятор накапливает возвращаемое значение обратного вызова; это накопленное значение, возвращенное при последнем вызове обратного вызова, или initialValue. -
currentValue
Элемент массива, который обрабатывается.
Редуктор и useReducer
Между редьюсером и useReducer есть огромное сходство.
reduce in JavaScript | useReducer in React |
---|---|
array.reduce(reducer, initialValue) |
useReducer(reducer, initialState) |
singleValue = reducer(accumulator, itemValue) |
newState = reducer(currentState, action) |
reduce method returns a single value | useReducer returns a pair of values. [newState, dispatch] |
Не имеет значения, если приведенная выше таблица непонятна в настоящее время, и в будущем она будет подробно объяснена с помощью примеров.
В этом разделе мы узнали:
-
useReducer
это хук API для управления состоянием. -
useReducer
связанные с редукторными функциями -
useReducer(reducer, initialState)
Принимает 2 параметра: функцию редуктора и начальное состояние. -
reducer(currentState, action)
Он также принимает 2 параметра, а именно текущее состояние и действие, и возвращает новое состояние.
simple state & action
В этом разделе мы рассмотрим встречный пример, чтобы изучить простое состояние и действие.
- import useReducer api
- объявить функцию редуктора и InitialState
- вызов для выполнения редуктора
CounterOne.tsx
import React, { useReducer } from 'react'
const initialState = 0
const reducer = (state: number, action: string) => {
switch (action) {
case 'increment':
return state + 1
case 'decrement':
return state - 1
case 'reset':
return initialState
default:
return state
}
}
function CounterOne() {
const [count, dispatch] = useReducer(reducer, initialState)
return (
<div>
<div>Count - {count}</div>
<button
onClick={() => dispatch('increment')}
>Increment</button>
<button
onClick={() => dispatch('decrement')}
>Decrement</button>
<button
onClick={() => dispatch('reset')}
>Reset</button>
</div>
)
}
export default CounterOne
App.tsx
import React from 'react'
import './App.css'
import CounterOne from './components/19CounterOne'
const App = () => {
return (
<div className="App">
<CounterOne />
</div>
)
}
export default App
Страница выглядит следующим образом:
После просмотра кода сначала импортируйте useReducer
import React, { useReducer } from 'react'
Затем вызовите useReducer
const [count, dispatch] = useReducer(reducer, initialState)
объявить редуктор, InitialState
const initialState = 0
const reducer = (state: number, action: string) => {
switch (action) {
case 'increment':
return state + 1
case 'decrement':
return state - 1
case 'reset':
return initialState
default:
return state
}
}
Двумя параметрами функции редуктора являются текущее состояние и действие, и они возвращают разные новые состояния в соответствии с разными действиями.
useReducer возвращает массив с двумя элементами для методов состояния и отправки. Состояние в нашем случае — это текущее значение счетчика, а метод диспетчеризации принимает параметр и выполняет соответствующее действие. После выполнения отправки соответствующее состояние изменится, и компонент будет повторно отображаться для отображения последнего состояния.
Это пример использования простого состояния и простого действия, в данном случае состояние — это числовой тип, а действие — простой строковый тип, который немного отличается от шаблона Redux. Далее мы рассмотрим немного более сложный пример.
complex state & action
Давайте посмотрим на второй пример. Будет использовать объекты в качестве значений состояния и действия, что больше похоже на шаблон, подобный Redux.
CounterTwo.tsx
import React, { useReducer } from 'react'
const initialState = {
firstCounter: 0
}
const reducer = (
state: {
firstCounter: number
},
action: {
type: string
}
) => {
switch (action.type) {
case 'increment':
return {
firstCounter: state.firstCounter + 1
}
case 'decrement':
return {
firstCounter: state.firstCounter - 1
}
case 'reset':
return initialState
default:
return state
}
}
function CounterTwo() {
const [count, dispatch] = useReducer(reducer, initialState)
return (
<div>
<div>Count - {count.firstCounter}</div>
<button
onClick={() => dispatch({ type: 'increment' })}
>Increment</button>
<button
onClick={() => dispatch({ type: 'decrement' })}
>Decrement</button>
<button
onClick={() => dispatch({ type: 'reset' })}
>Reset</button>
</div>
)
}
export default CounterTwo
App.tsx
import React from 'react'
import './App.css'
import CounterTwo from './components/20CountTwo'
const App = () => {
return (
<div className="App">
<CounterTwo />
</div>
)
}
export default App
Страница выглядит следующим образом:
Тот же эффект, что и в примере из предыдущего раздела. Теперь, когда мы переписали и состояние, и действие как объекты, какая от этого польза?
Одним из преимуществ является то, что действие теперь является объектом и может иметь несколько свойств, определяющих эффект действия. Например, давайте добавим еще одну логику +5.
CounterTwo.tsx
import React, { useReducer } from 'react'
const initialState = {
firstCounter: 0
}
const reducer = (
state: {
firstCounter: number
},
action: {
type: string
value: number
}
) => {
switch (action.type) {
case 'increment':
return {
firstCounter: state.firstCounter + action.value
}
case 'decrement':
return {
firstCounter: state.firstCounter - action.value
}
case 'reset':
return initialState
default:
return state
}
}
function CounterTwo() {
const [count, dispatch] = useReducer(reducer, initialState)
return (
<div>
<div>Count - {count.firstCounter}</div>
<button
onClick={() => dispatch({
type: 'increment',
value: 1
})}
>Increment</button>
<button
onClick={() => dispatch({
type: 'decrement',
value: 1
})}
>Decrement</button>
<button
onClick={() => dispatch({
type: 'increment',
value: 5
})}
>Increment 5</button>
<button
onClick={() => dispatch({
type: 'decrement',
value: 5
})}
>Decrement 5</button>
<button
onClick={() => dispatch({ type: 'reset', value: 0})}
>Reset</button>
</div>
)
}
export default CounterTwo
Страница выглядит следующим образом:
Можно заметить, что к действию добавлен атрибут value для реализации логики прибавления или вычитания 5.
Второе преимущество заключается в том, что в качестве объекта можно добавить больше свойств состояния, например, мы добавляем счетчик 2. Код выглядит следующим образом:
import React, { useReducer } from 'react'
const initialState = {
firstCounter: 0,
secondCounter: 10,
}
const reducer = (
state: {
firstCounter: number
secondCounter: number
},
action: {
type: string
value: number
}
) => {
switch (action.type) {
case 'increment':
return {
...state,
firstCounter: state.firstCounter + action.value
}
case 'decrement':
return {
...state,
firstCounter: state.firstCounter - action.value
}
case 'increment2':
return {
...state,
secondCounter: state.secondCounter + action.value
}
case 'decrement2':
return {
...state,
secondCounter: state.secondCounter - action.value
}
case 'reset':
return initialState
default:
return state
}
}
function CounterTwo() {
const [count, dispatch] = useReducer(reducer, initialState)
return (
<div>
<div>First Count - {count.firstCounter}</div>
<div>Second Count - {count.secondCounter}</div>
<button
onClick={() => dispatch({
type: 'increment',
value: 1
})}
>Increment</button>
<button
onClick={() => dispatch({
type: 'decrement',
value: 1
})}
>Decrement</button>
<button
onClick={() => dispatch({
type: 'increment',
value: 5
})}
>Increment 5</button>
<button
onClick={() => dispatch({
type: 'decrement',
value: 5
})}
>Decrement 5</button>
<div>
<button
onClick={() => dispatch({
type: 'increment2',
value: 1
})}
>Increment second</button>
<button
onClick={() => dispatch({
type: 'decrement2',
value: 1
})}
>Decrement second</button>
</div>
<button
onClick={() => dispatch({ type: 'reset', value: 0 })}
>Reset</button>
</div>
)
}
export default CounterTwo
Эффект отображения страницы выглядит следующим образом
Таким образом, мы можем поддерживать 2 таймера одновременно.
multiple useReducers
UseReducer можно использовать несколько раз, если есть несколько состояний, но состояние изменяется одинаково.
CounterThree.tsx
import React, { useReducer } from 'react'
const initialState = 0
const reducer = (state: number, action: string) => {
switch (action) {
case 'increment':
return state + 1
case 'decrement':
return state - 1
case 'reset':
return initialState
default:
return state
}
}
function CounterThree() {
const [count, dispatch] = useReducer(reducer, initialState)
const [countTwo, dispatchTwo] = useReducer(reducer, initialState)
return (
<div>
<div>Count - {count}</div>
<button
onClick={() => dispatch('increment')}
>Increment</button>
<button
onClick={() => dispatch('decrement')}
>Decrement</button>
<button
onClick={() => dispatch('reset')}
>Reset</button>
<br/>
<div>CountTwo - {countTwo}</div>
<button
onClick={() => dispatchTwo('increment')}
>Increment</button>
<button
onClick={() => dispatchTwo('decrement')}
>Decrement</button>
<button
onClick={() => dispatchTwo('reset')}
>Reset</button>
</div>
)
}
export default CounterThree
Страница отображается следующим образом
В этом примере используется несколько useReducers, но используется общая функция редьюсера. Это позволяет избежать проблем с объединением объектов (сравните использование алгоритма раскрутки для состояния слияния в предыдущем разделе). Это также улучшает возможность повторного использования кода.
useReducer with useContext
До сих пор мы научились использовать useReducer для управления состоянием внутри компонента. Если мы хотим разделить состояние между компонентами в некоторых сценариях и выполнить глобальное управление состоянием, мы можем использовать useReducer и useContext.
Рассмотрим такой сценарий, есть 3 подкомпонента A, B, C, для управления одним и тем же счетчиком в подкомпоненте, обычный способ записи - записать метод счетчика в родительский компонент, а затем передать метод счетчика и состояние через реквизиты.Для дочернего компонента вызов метода счетчика, переданного через реквизиты в дочернем компоненте, изменит состояние в родительском компоненте, а также состояние в приложении, переданном дочернему компоненту в качестве реквизита. Как показано ниже:
Вроде бы все хорошо, но если компоненты глубоко вложены, это будет очень плохо, и вам придется передавать метод счетчика в качестве реквизита дочерним компонентам слой за слоем. На данный момент нам нужно использовать useContext и useReducer.
Выполнение этого требования разделено на 2 этапа.
- Создайте метод счетчика на корневом узле, используя useReducer
- Предоставление и использование контекста дочерним компонентам через useContext
App.tsx
import React, { useReducer } from 'react'
import './App.css'
import A from './components/22A'
import B from './components/22B'
import C from './components/22C'
interface CountContextType {
countState: number
countDispatch: (action: string) => void
}
export const CountContext = React.createContext({} as CountContextType)
const initialState = 0
const reducer = (state: number, action: string) => {
switch (action) {
case 'increment':
return state + 1
case 'decrement':
return state - 1
case 'reset':
return initialState
default:
return state
}
}
const App = () => {
const [count, dispatch] = useReducer(reducer, initialState)
return (
<CountContext.Provider
value={{
countState: count,
countDispatch: dispatch,
}}
>
<div className="App">
Count - {count}
<A />
<B />
<C />
</div>
</CountContext.Provider>
)
}
export default App
A.tsx
import React, { useContext } from 'react'
import { CountContext } from '../App'
function A() {
const countContext = useContext(CountContext)
return (
<div>
A - {countContext.countState}
<button
onClick={() => countContext.countDispatch('increment')}
>Increment</button>
<button
onClick={() => countContext.countDispatch('decrement')}
>Decrement</button>
<button
onClick={() => countContext.countDispatch('reset')}
>Reset</button>
</div>
)
}
export default A
B.tsx
import React from 'react'
import D from './22D'
function B() {
return (
<div>
<D />
</div>
)
}
export default B
C.tsx
import React from 'react'
import E from './22E'
function C() {
return (
<div>
<E />
</div>
)
}
export default C
D.tsx
import React, { useContext } from 'react'
import { CountContext } from '../App'
function D() {
const countContext = useContext(CountContext)
return (
<div>
D - {countContext.countState}
<button
onClick={() => countContext.countDispatch('increment')}
>Increment</button>
<button
onClick={() => countContext.countDispatch('decrement')}
>Decrement</button>
<button
onClick={() => countContext.countDispatch('reset')}
>Reset</button>
</div>
)
}
export default D
E.tsx
import React from 'react'
import F from './22F'
function E() {
return (
<div>
<F />
</div>
)
}
export default E
F.tsx
import React, { useContext } from 'react'
import { CountContext } from '../App'
function F() {
const countContext = useContext(CountContext)
return (
<div>
F - {countContext.countState}
<button
onClick={() => countContext.countDispatch('increment')}
>Increment</button>
<button
onClick={() => countContext.countDispatch('decrement')}
>Decrement</button>
<button
onClick={() => countContext.countDispatch('reset')}
>Reset</button>
</div>
)
}
export default F
Эффект страницы выглядит следующим образом
Давайте рассмотрим еще раз
- В App.tsx мы используем useReducer для создания счетчика, объявления начального значения, создания функции редуктора, useReducer возвращает количество состояний и метод отправки.
- Чтобы разрешить другим компонентам доступ к подсчету и отправке, мы создаем CountContext через React.createContext и используем
<CountContext.Provider>
Оберните корневой узел. Передайте количество и отправьте как значение провайдеру. - В дочернем узле мы используем useContext для получения методов подсчета и отправки, а также меняем подсчет, вызывая отправку.
Fetching Data with useReducer
Мы узнали, как запрашивать данные в главе useEffect раньше, используя useEffect и useState в то время, теперь давайте посмотрим, как использовать useReducer для запроса удаленных данных.
Далее делаем такое небольшое требование:
- Запросить данные при загрузке страницы
- Показать состояние загрузки в данных запроса
- После возврата запроса удалите стиль загрузки и отобразите запрошенные данные; если запрос не выполнен, удалите загрузку и отобразите сообщение об ошибке
Мы будем использовать useState и useReducer соответственно для реализации и сравнения различий.
useState реализует запрос
App.tsx
import React from 'react'
import './App.css'
import DataFetchingOne from './components/23DataFetchingOne'
const App = () => {
return (
<div className="App">
<DataFetchingOne />
</div>
)
}
export default App
DataFetchingOne.tsx
import React, { useState, useEffect } from 'react'
import axios from 'axios'
interface postType {
userId: number
id: number
title: string
body: string
}
function DataFetchingOne() {
const [loading, setLoading] = useState(true)
const [error, setError] = useState('')
const [post, setPost] = useState({} as postType)
useEffect(() => {
axios.get('https://jsonplaceholder.typicode.com/posts/1').then((res) => {
setLoading(false)
setPost(res.data)
setError('')
}).catch(() => {
setLoading(false)
setPost({} as postType)
setError('something went wrong')
})
}, [])
return (
<div>
{
loading
? 'Loading...'
: post.title
}
{
error
? error
: null
}
</div>
)
}
export default DataFetchingOne
Эффект страницы выглядит следующим образом
Мы намеренно изменили ссылку запроса axios и видим логику ввода ошибки следующим образом.
Обратите внимание, что в этой реализации мы используем 3 useStates для управления загрузкой, публикацией и ошибкой, давайте посмотрим, как использовать реализацию useReducer.
useReducer реализует запрос
App.tsx
import React from 'react'
import './App.css'
import DataFetchingOne from './components/23DataFetchingOne'
const App = () => {
return (
<div className="App">
<DataFetchingOne />
</div>
)
}
export default App
import React, { useEffect, useReducer } from 'react'
import axios from 'axios'
interface postType {
userId: number
id: number
title: string
body: string
}
type stateType = {
loading: boolean
error: string
post?: postType | {}
}
type actionType = {
type: 'FETCH_SUCCESS' | 'FETCH_ERROR'
payload?: postType | {}
}
const initialState = {
loading: true,
error: '',
post: {},
}
const reducer = (state: stateType, action: actionType) => {
switch (action.type) {
case 'FETCH_SUCCESS':
return {
loading: false,
error: '',
post: action.payload,
}
case 'FETCH_ERROR':
return {
loading: false,
error: 'something went wrong',
post: {},
}
default:
return state
}
}
function DataFetchingTwo() {
const [state, dispatch] = useReducer(reducer, initialState)
useEffect(() => {
axios.get('https://jsonplaceholder.typicode.com/posts/1').then((res) => {
dispatch({
type: 'FETCH_SUCCESS',
payload: res.data,
})
}).catch(() => {
dispatch({
type: 'FETCH_ERROR'
})
})
}, [])
return (
<div>
{
state.loading
? 'Loading...'
// @ts-ignore
: state.post.title
}
{
state.error
? state.error
: null
}
</div>
)
}
export default DataFetchingTwo
Эффект отображения страницы такой же, как и в предыдущем примере.
Как видите, мы агрегируем состояние вместе, и в этом же объекте вместе агрегируется и логика модификации состояния, то есть переключающая часть функции редуктора.
На данный момент вам может быть интересно, когда использовать useState и когда использовать useReducer, давайте продолжим смотреть.
useState vs useReducer
- Если тип состояния Number, String, Boolean, рекомендуется использовать useState, если тип состояния Object или Array, рекомендуется использовать useReducer
- Если состояние сильно меняется, также рекомендуется использовать useReducer для централизованного управления изменениями состояния и облегчения обслуживания.
- Если ассоциация состояния изменяется, рекомендуется использовать useReducer
- Если бизнес-логика очень сложная, также рекомендуется использовать useReducer.
- Если состояние используется только внутри компонента, рекомендуется использовать useState, а если вы хотите сохранить глобальное состояние, рекомендуется использовать useReducer.
Scenario | useState | useReducer |
---|---|---|
Type of state | Number, String, Boolean | Object or Array |
Number of state transitions | 1 or 2 | Too many |
Related state transitions | No | Yes |
Business logic | No business logic | Complex business logic |
local vs global | local | global |
резюме
В этой главе в основном описывается использование useReducer. Начиная с API сокращения в JavaScript, сравнение показывает, что такое useReducer.
Изучил использование простых состояний useReducer, использование сложных состояний и использование нескольких useReducer.
Я освоил метод использования useContext и useReducer для изменения глобального состояния подкомпонентов, когда компоненты вложены в несколько слоев, код стал более элегантным и удобным в сопровождении.
Сравнив useState, я узнал, как использовать useEffect и useReducer для запроса данных и управления отображением и скрытием состояния загрузки.
Наконец, сравниваются useState и useReducer и даются предложения по использованию.