Можно ли управлять состоянием без 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.