Можно ли управлять состоянием без useState
в React? Да-да... Вам всегда нужно использовать useState
для обработки состояний. Нет.. Вы не знаете. Позвольте представить вам крючокuseReducer
. В этой статье мы рассмотрим, что такое хук usereducer
и почему это лучший выбор для управления сложными состояниями, чем хук useState
.
Почти все проекты React использовали Redux для обработки сложных состояний перед выпуском хуков. Redux способен обрабатывать сложное общее состояние, делясь им глобально. Но после введения Context API и useReducer
hook Redux больше не требуется. Я буду обсуждать useContext
hook и Context API в одной из своих последних статей.
Преобразование компонента из useState
в useReducer
Прежде всего, давайте посмотрим, как мы создавали приложение React до выхода useReducer
. Здесь я собираюсь представить вам небольшой сценарий приложения.
Это то, что мы собираемся построить. Не причудливый. Но этого более чем достаточно, чтобы показать вам, как все работает с концепцией useReducer
. В этом приложении вы можете добавлять новые задачи, удалять их и выполнять.
Это код сценария.
import { useState } from "react"; function App() { const [inputValue, setInputValue] = useState(""); const [tasks, setTasks] = useState([]); const addTask = () => { const lastId = tasks.length; const newTask = { id: lastId + 1, title: inputValue, status: false, }; setTasks([...tasks, newTask]); setInputValue(""); }; const deleteTask = (id) => { setTasks(tasks.filter((task) => task.id != id)); }; const completeTask = (id) => { const updatedTasks = tasks.map((task) => { if (task.id === id) { task.status = true; } return task; }); setTasks(updatedTasks); }; return ( <div className="App"> <div className="input-section"> <label>Task</label> <input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} placeholder="Add Task..." /> <button onClick={addTask}>ADD</button> </div> {tasks?.map((task) => ( <div key={task.id} className="task-section"> <h3 style={{ textDecoration: task.status ? "line-through" : "none" }}> {task.title} </h3> <button onClick={() => deleteTask(task.id)} className="delete-button"> DELETE </button> <button onClick={() => completeTask(task.id)} className="complete-button" > {task.status === true ? "COMPLETED" : "COMPLETE"} </button> </div> ))} </div> ); } export default App;
Вы можете сохранить значение, используя эти состояния.
const [inputValue, setInputValue] = useState(""); const [tasks, setTasks] = useState([]);
И вы можете использовать отдельные функции для каждого действия. Однако с useReducer
вы можете сделать это классным способом.
Так же, как useState
, useReducer
также имеет начальное состояние и способ обновить это состояние (содержит всю логику для изменения нашего состояния) в качестве аргументов. Он принимает начальное состояние и возвращает текущее состояние. Он повторно отображает компонент при изменении состояния, как useState.
const [state, dispatch] = useReducer(reducer, { tasks: [], inputValue: "" });
{ task: [], inputValue: “” }
— это начальное состояние, а из state
мы можем получить доступ к текущему состоянию. Вместо setState
здесь мы используем dispatch
функцию, и это помогает нам вызывать функции редуктора, которые хранятся внутри reducer.
. Эти функции используются для обработки нашего состояния. Давайте построим наш reducer
, тогда вы получите четкое представление.
function reducer(state, action) { switch (action.type) { case "add task": return "add task"; case "delete task": return "delete task"; default: return state; } }
Прежде чем перейти к фактической реализации, я придумал простой редуктор. Как видите, функция reducer
имеет два параметра, например state
и action
. state
представляет текущее состояние, а action
представляет действие, которое мы вызываем для выполнения. action
имеет два свойства, такие как type
и thepayload
. Тип — это тип действия, которое мы собираемся выполнить. Здесь выше фрагмент кода «добавить задачу» и «удалить задачу» — это типы действий.
Мы можем вызвать это действие, используя функцию dispatch
.
dispatch({ type: "add input", payload: e.target.value }); dispatch({ type: "add task", payload: state.inputValue }); dispatch({ type: "delete task", payload: task.id }); dispatch({ type: "complete task", payload: task.id })
И при этом это фактическая функция reducer
для этого приложения.
function reducer(state, action) { switch (action.type) { case "add input": return { ...state, inputValue: action.payload }; case "add task": const lastId = state.tasks.length; const newTask = { id: lastId + 1, title: action.payload, status: false, }; state.inputValue = ""; return { ...state, tasks: [...state.tasks, newTask] }; case "delete task": return { ...state, tasks: state.tasks.filter((task) => task.id != action.payload), }; case "complete task": const updatedTasks = state.tasks.map((task) => { if (task.id === action.payload) { task.status = true; } return task; }); return { ...state, tasks: updatedTasks, }; default: return state; } }
Есть небольшой момент, над которым нам нужно поработать. Это тип действия. Типы действий определяются как строки. Таким образом, это позволяет легко случайно ошибиться в типе действия и вызвать ошибку. Поэтому мы можем решить эту проблему, создав постоянные типы действий.
const ACTION_TYPE = { ADD_INPUT: "add input", ADD_TASK: "add task", DELETE_TASK: "delete task", COMPLETE_TASK: "complete task", };
Это полный код приложения после добавления постоянных типов действий.
import { useReducer, useState } from "react"; function reducer(state, action) { switch (action.type) { case ACTION_TYPE.ADD_INPUT: return { ...state, inputValue: action.payload }; case ACTION_TYPE.ADD_TASK: const lastId = state.tasks.length; const newTask = { id: lastId + 1, title: action.payload, status: false, }; state.inputValue = ""; return { ...state, tasks: [...state.tasks, newTask] }; case ACTION_TYPE.DELETE_TASK: return { ...state, tasks: state.tasks.filter((task) => task.id != action.payload), }; case ACTION_TYPE.COMPLETE_TASK: const updatedTasks = state.tasks.map((task) => { if (task.id === action.payload) { task.status = true; } return task; }); return { ...state, tasks: updatedTasks, }; default: return state; } } const ACTION_TYPE = { ADD_INPUT: "add input", ADD_TASK: "add task", DELETE_TASK: "delete task", COMPLETE_TASK: "complete task", }; function App() { const [state, dispatch] = useReducer(reducer, { tasks: [], inputValue: "" }); console.log(state); return ( <div className="App"> <div className="input-section"> <label>Task</label> <input type="text" value={state.inputValue} onChange={(e) => dispatch({ type: ACTION_TYPE.ADD_INPUT, payload: e.target.value }) } placeholder="Add Task..." /> <button onClick={() => dispatch({ type: ACTION_TYPE.ADD_TASK, payload: state.inputValue }) } > ADD </button> </div> {state.tasks?.map((task) => ( <div key={task.id} className="task-section"> <h3 style={{ textDecoration: task.status ? "line-through" : "none" }}> {task.title} </h3> <button onClick={() => dispatch({ type: ACTION_TYPE.DELETE_TASK, payload: task.id }) } className="delete-button" > DELETE </button> <button onClick={() => dispatch({ type: ACTION_TYPE.COMPLETE_TASK, payload: task.id }) } className="complete-button" > {task.status === true ? "COMPLETED" : "COMPLETE"} </button> </div> ))} </div> ); } export default App;
Как видите, наш компонент больше не состоит из сложного кода. Компонент направляет все наши действия на редюсер. Редуктор управляет всей сложной логикой. Это отлично, потому что позволяет повторно использовать и совместно использовать состояние между компонентами, отделив логику состояния от самого компонента.
Итак, об этом я и собираюсь поговорить. Я не говорил, что useState
плохой, а useReducer
лучший. Надеюсь, вы не уловили эту мысль. Только я сказал, что вы можете обрабатывать состояние с помощью useState
, пока оно не усложнится. Перейдите к useReducer
, когда он станет сложным.
Увидимся в следующем посте блога. До свидания🍻🍸❤️❤️
Дополнительные материалы на PlainEnglish.io.
Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord.
Повысьте узнаваемость и признание вашего технического стартапа с помощью Circuit.