Хук useReducer
React сложен и поначалу немного сложен для масштабирования. Тем не менее, после того, как вы разберетесь с этим хуком и узнаете, как его использовать, управление состоянием становится настолько простым, насколько это возможно, особенно в аспекте отслеживания различных частей состояний, реализованных с помощью хука useState
.
В этой статье мы рассмотрим хук useReducer
и почему он лучше подходит для управления сложными состояниями в React, чем хук useState
. Это руководство подходит для начинающих, и вам необходимо установить Node.js и React.
Государственное управление
Мы часто слышим о состоянии, изменениях состояния и управлении состоянием. Что такое государство? Мы можем понять это буквально, сказав, что это текущее состояние вашей программы, но это может быть не так просто понять. В программировании состояние — это просто комбинация всех данных, которые у нас есть в настоящее время в нашем приложении, данных, которые используются и возвращаются вашей текущей программой.
Что такое государственное управление? Согласно Википедии, «управление состоянием относится к управлению состоянием одного или нескольких элементов управления пользовательского интерфейса, таких как текстовые поля, кнопки OK, переключатели и т. д.».
useReduce против useState
Можно ли управлять состоянием без использования useState
? Распространенный вопрос с более чем 300 000 результатов в Google:
Если вы обнаружите, что отслеживаете несколько частей состояния, основанных на сложной логике, хук useReducer
может быть лучше. Давайте создадим приложение, которое может увеличивать и уменьшать числа с помощью этого хука, и посмотрим, насколько оно может быть эффективным.
Настройка области разработки
Нам нужно запустить это:
npx create-react-app counter
cd counter
npm start
После установки у нас должно получиться вот это.
использованиередьюсер
const [state, dispatch] = useReducer(reducer, initialArg, init);
UseReducer возвращает массив, первый элемент которого является состоянием, а второй элемент — функцией отправки, которая вызывает useReducer.
Для сборки приложения-счетчика нам нужны четыре файла: основной App.js
для рендеринга наших компонентов; Counter.js
для нашего счетчика приложений; Reducer.js
где мы будем управлять состоянием приложения, используя нашу логику useReducer; и наш Styles.css
. Возникают вопросы: что мы делаем? Как мы управляем государством? Какие преимущества это даст по сравнению с useState
? А вопросы продолжаются. На все эти вопросы я отвечу в этой статье.
Повтор сеанса с открытым исходным кодом
OpenReplay – это пакет для воспроизведения сеансов с открытым исходным кодом, который позволяет вам видеть, что пользователи делают в вашем веб-приложении, помогая вам быстрее устранять неполадки. OpenReplay размещается на собственном сервере для полного контроля над вашими данными.
Начните получать удовольствие от отладки — начните использовать OpenReplay бесплатно.
Приложение счетчика
Вот как мы могли бы начать это.
const [count, dispatch] = useReducer(reducer, 0);
Вместо сеттера в useState
мы использовали dispatch
. «Отправка» здесь имеет буквальное значение, больше похожее на то, что вы хотите что-то отправить: вы можете сказать «отправить действие». Мы обработаем его функцией редуктора. Как мы видим, у нас есть состояние 0. Давайте начнем создавать приложение-счетчик.
//counter.js
import React, { useReducer } from "react";
import reducer from "./Reducer";
function Counter() {
const [count, dispatch] = useReducer(reducer, 0);
return (
<div className="container">
<div className="card">
<h1>Counter Application</h1>
<h3>{count}</h3>
<div>
<button className="btn1" onClick={() => dispatch("increment")}>
increment
</button>
<button className="btn2" onClick={() => dispatch("decrement")}>
decrement
</button>
<button className="btn3" onClick={() => dispatch("reset")}>
Reset
</button>
</div>
</div>
</div>
);
}
export default Counter;
У нас также есть:
//reducer.js
const reducer = (state, action) => {
if (action === "increment") {
return state + 1;
} else if (action === "decrement") {
return state - 1;
} else if (action === "reset") {
return 0;
} else {
throw new Error();
}
};
export default reducer;
И стиль:
//styles.css
.container {
display: flex;
align-items: center;
justify-content: center;
}
h3 {
display: flex;
align-items: center;
justify-content: center;
}
.btn1 {
background-color: blue;
margin: 20px;
color: beige;
}
.btn2 {
background-color: red;
margin: 20px;
color: beige
}
.btn3 {
background-color: green;
margin: 20px;
color: beige
}
Наконец, наш основной файл App
.
//App.js
import React from "react";
import "./styles.css";
import Counter from "./Counter";
function App() {
return (
<div>
<Counter />
</div>
);
}
export default App;
В приведенном выше коде показано приложение-счетчик, состояние которого управляется хуком useReducer. Счетчик мало что говорит о сложном управлении состоянием, но затем я объясню использованную выше логику. Редуктор принимает наше состояние и отправленное действие. В Reducer.js
функция редуктора принимает наше состояние, и действие отправляется, затем мы используем наш условный оператор. У нас было if-else
, а прошло action.type
. Мы передали increment
, decrement
и reset
функции onclick
в JSX.
Теперь мы можем протестировать наше приложение, если оно работает хорошо. Изначально имеем:
После некоторых приращений получаем:
Именно здесь мы начинаем видеть, как useReducer
проливает на нас свой свет. Возможно, мы не заметили, что полностью отделили логику обновления нашего состояния от нашего компонента. Теперь мы сопоставляем действия с переходами состояний, и теперь мы можем отделить то, как обновляется состояние, от произошедших действий. (Мы рассмотрим более практические преимущества этого позже.) А пока давайте добавим в наше приложение более сложные функции, чтобы лучше объяснить, насколько удобным может быть useReducer
.
Вместо того, чтобы просто увеличивать и уменьшать на 1, давайте сделаем ползунок, где пользователь может выбрать значение, которое он хочет увеличить или уменьшить, в диапазоне от 1 до 100.
import React, { useState } from "react";
function Slider({ onchange, min, max }) {
const [value, setvalue] = useState(1);
return (
<div className="slide">
{value}
<input
type="range"
min={min}
max={max}
value={value}
onChange={(e) => {
const value = Number(e.target.value);
onchange(value);
setvalue(value);
}}
/>
</div>
);
}
export default Slider;
Нам нужно импортировать это в наш Counter.js, чтобы его можно было отобразить в браузере. Мы также будем передавать реквизиты min
, max
и onchange
, присваивая им значения.
import Slide from "./Slide";
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0, step: 1 });
return (
<div className='container'>
<div className='card'>
<h1>Counter Application</h1>
<h3>{state.count}</h3>
<div >
<button className='btn1' onClick={() => dispatch('increment')}>increment</button>
<button className='btn2' onClick={() => dispatch('decrement')}>decrement</button>
<button className='btn3' onClick={() => dispatch('reset')}>Reset</button>
</div>
<div>
<Slide
min={1}
max={100}
onchange={()=>({})}
/>
</div>
</div>
</div>
);
}
Вот что мы получаем.
Теперь мы можем получить значение ползунка из реквизита onChange
. Это позволяет нам решить, насколько мы можем увеличивать и уменьшать значения. Нам нужно внести несколько изменений, чтобы управлять частью состояния значения нашего ползунка и позволить нашему ползунку определять, что мы будем увеличивать или уменьшать.
Давайте сделаем наше состояние объектом: таким образом, любая новая часть состояния, которой должен управлять наш Редюсер, может стать свойством этого объекта. Во-первых, мы меняем наше начальное состояние на объект.
const [state, dispatch] = useReducer(reducer, { count: 0, move: 1 });
Наше государство — это объект. Нам нужно обновить наш редьюсер, чтобы он возвращал объект с двумя свойствами.
const reducer = (state, action) => {
if (action === "increment") {
return {
count: state.count + 1,
move: state.move,
};
} else if (action === "decrement") {
return {
count: state.count - 1,
move: state.move,
};
} else if (action === "reset") {
return {
count: 0,
move: state.move,
};
} else {
throw new Error();
}
};
export default reducer;
Вернувшись в счетчик, нам нужно передать состояние нашему JSX
.
<h3>{state.count}</h3>
Это прекрасно работает, но вместо того, чтобы наше состояние было целым числом, мы теперь имеем его как объект, что позволяет нам передавать другие свойства. Теперь возникает вопрос: что вы хотите отправить в onChange
для обновления состояния нашего редуктора? До сих пор мы могли отправлять тип произошедшего действия (увеличение, уменьшение или сброс). Это сработало просто отлично, но теперь мы сталкиваемся с его ограничениями. Наряду с action type
нужны еще некоторые данные. В частности, нам нужно передать значение слайда, чтобы добавить его к нашему значению состояния и обновить состояние. Вместо того, чтобы передавать наше действие в виде строки, давайте изменим его на объект со свойством типа. Таким образом, мы все еще можем выполнять диспетчеризацию в зависимости от типа действия. Мы сможем передать значение ползунка и любые другие данные в качестве свойства объекта действия. Мы можем перейти к нашей опоре onChange и сделать это прямо сейчас.
<Slide
min={1}
max={100}
onchange={(value) =>
dispatch({
type: "stepUpdate",
step: value,
})
}
/>;
Нам нужно внести три изменения в наш Редюсер:
- Нам нужно обновить инкремент и декремент, чтобы настроить счетчик на основе свойства шага, а не только на 1. Мы делаем это, обновляя его любым
move
. - Нам нужно учесть наш новый тип действия
moveUpdate
, добавив для него случай в наш редюсер. - Нам нужно изменить
action
, чтобы он был объектом, а не строкой, передав свойство типа только нашему новому случаю.
Давайте сделаем эти быстрые исправления.
//Reducer.js
const reducer = (state, action) => {
if (action === "increment") {
return {
count: state.count + state.move,
move: state.move,
};
} else if (action === "decrement") {
return {
count: state.count - state.move,
move: state.move,
};
} else if (action === "reset") {
return {
count: 0,
move: state.move,
};
} else if (action.type === "moveUpdate") {
return {
count: state.count,
move: action.move,
};
} else {
throw new Error();
}
};
export default reducer;
Теперь мы можем обновить значение счетчика с помощью ползунка — например, увеличить от нуля сначала на 31, а затем на 48.
Заключение
Мы подошли к концу статьи, но мне нужно разъяснить кое-что очень важное. Мы увидели невероятное и мощное преимущество useReducer
, которое вы, возможно, упустили: функция редюсера передала текущее состояние в качестве первого аргумента. Из-за этого просто обновить одну часть состояния в зависимости от значения другой части состояния. Для этого вам нужно будет использовать крючок useReducer
вместо useState
. В нашем примере мы могли видеть это при обновлении count
на основе значения `move '.
Другие статьи из блога OpenReplay
Голосовые формы в React с помощью Speechly
Узнайте, как заполнить форму, говоря с помощью React и Speechly
4 мая 2022 г. · 4 минуты чтения
Первоначально опубликовано на blog.openreplay.com 5 мая 2022 г.