JavaRush /Курсы /Модуль 3: React /Создание глобального состояния с useContext и useReducer

Создание глобального состояния с useContext и useReducer

Модуль 3: React
2 уровень , 5 лекция
Открыта

Создание глобального состояния

Настало время объединить поученные знания и создать глобальное состояние, используя оба подхода, которые нам уже известны: useContext и useReducer.

Окей, представьте, что вы строите огромный дом (читай: приложение). У вас есть много комнат (или компонентов), и все они делят один Wi-Fi. Конечно, вы могли бы тащить роутер в каждую комнату (передавать пропсы через дерево компонентов), но почему бы не поставить один мощный роутер (использовать глобальное состояние) и не раздавать интернет всем сразу?

Вот тут-то и вступает в игру сочетание useContext и useReducer. С их помощью мы создадим единое хранилище данных, доступное любому компоненту нашего приложения.

Зачем это нужно?

Во-первых, использование комбинации useContext и useReducer — это эффективный способ управления состоянием на уровне всего приложения. Это похоже на создание центра управления, вместо того чтобы разбрасывать управление данными по всему дереву компонентов.

Во-вторых, это делает наш код более читаемым и структурированным. Вы можете сконцентрировать все логику управления состоянием в одном месте, что особенно полезно в больших проектах.

Интеграция useReducer с useContext

Начнем с создания глобального состояния. Для примера мы построим простое приложение задач (todo list), где каждый пользователь сможет добавлять, удалять и отмечать задачи как выполненные.

1. Создаем редьюсер

Сначала определим редьюсер. Редьюсер — это функция, которая определяет, как изменяется наше состояние в ответ на действия.

// types.ts
export interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

export type State = {
  todos: Todo[];
};

export type Action =
  | { type: "ADD_TODO"; payload: string }
  | { type: "REMOVE_TODO"; payload: number }
  | { type: "TOGGLE_TODO"; payload: number };

// reducer.ts
const todoReducer = (state: State, action: Action): State => {
  switch (action.type) {
    case "ADD_TODO":
      const newTodo = {
        id: Date.now(), // уникальный id
        text: action.payload,
        completed: false,
      };
      return { ...state, todos: [...state.todos, newTodo] };
    case "REMOVE_TODO":
      return {
        ...state,
        todos: state.todos.filter((todo) => todo.id !== action.payload),
      };
    case "TOGGLE_TODO":
      return {
        ...state,
        todos: state.todos.map((todo) =>
          todo.id === action.payload
            ? { ...todo, completed: !todo.completed }
            : todo
        ),
      };
    default:
      throw new Error(`Unhandled action type: ${action.type}`);
  }
};

export default todoReducer;

2. Создаем контекст

Контекст нужен, чтобы сделать данные нашего редьюсера доступными в любом компоненте. Мы создадим контекст, который будет содержать как состояние, так и функции для диспатча действий.

// context.tsx
import React, { createContext, useReducer, ReactNode } from "react";
import todoReducer, { State, Action } from "./reducer";

const initialState: State = {
  todos: [],
};

// Создаем контекст
const TodoContext = createContext<{
  state: State;
  dispatch: React.Dispatch<Action>;
}>({
  state: initialState,
  dispatch: () => null, // временный заглушка
});

// Провайдер контекста
const TodoProvider = ({ children }: { children: ReactNode }) => {
  const [state, dispatch] = useReducer(todoReducer, initialState);

  return (
    <TodoContext.Provider value={{ state, dispatch }}>
      {children}
    </TodoContext.Provider>
  );
};

export { TodoContext, TodoProvider };

3. Подключение провайдера

Теперь подключим TodoProvider к нашему приложению. Это позволит всем компонентам внутри TodoProvider получить доступ к глобальному состоянию.

// App.tsx
import React from "react";
import { TodoProvider } from "./context";
import TodoList from "./TodoList";

function App() {
  return (
    <TodoProvider>
      <TodoList />
    </TodoProvider>
  );
}

export default App;

Использование глобального состояния

Теперь мы готовы использовать наше глобальное состояние. Для этого подключимся к контексту с помощью хука useContext.

4. Используем контекст в компоненте

Создадим компонент для отображения списка задач и добавления новых.

// TodoList.tsx
import React, { useContext, useState } from "react";
import { TodoContext } from "./context";

const TodoList = () => {
  const { state, dispatch } = useContext(TodoContext);
  const [newTodo, setNewTodo] = useState("");

  const addTodo = () => {
    if (newTodo.trim()) {
      dispatch({ type: "ADD_TODO", payload: newTodo });
      setNewTodo("");
    }
  };

  return (
    <div>
      <h1>Todo List</h1>
      <input
        type="text"
        value={newTodo}
        onChange={(e) => setNewTodo(e.target.value)}
      />
      <button onClick={addTodo}>Add Task</button>
      <ul>
        {state.todos.map((todo) => (
          <li key={todo.id}>
            <span
                    style={{
                    textDecoration: todo.completed ? "line-through" : "none",
              }}
              onClick={() => dispatch({ type: "TOGGLE_TODO", payload: todo.id })}
            >
              {todo.text}
            </span>
            <button onClick={() => dispatch({ type: "REMOVE_TODO", payload: todo.id })}>
              Delete
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default TodoList;

5. Вот и готово ваше Todo-приложение!

Теперь вы можете добавлять, удалять и отмечать задачи как выполненные. А самое важное — наше состояние централизовано и доступно через контекст.

Плюсы и минусы такого подхода

Плюсы глобального состояния с использованием useContext и useReducer:

  1. Простота: все состояния и логика управления находятся в одном месте.
  2. Масштабируемость: легко добавлять новые действия и типы данных.
  3. Типизация: TypeScript делает наш код надежным и предсказуемым.

Однако не забывайте о следующих потенциальных проблемах:

  1. Если приложение становится большим, контекст может перестать быть оптимальным по производительности — в таком случае стоит рассмотреть Redux или другие библиотеки.
  2. Частые ререндеры: при изменении состояния весь контекст вызывает обновление, даже если некоторые компоненты не зависят от новых данных.

Теперь вы готовы перенести управление состоянием вашего приложения на новый уровень. useContext и useReducer — потрясающее комбо для небольших и средних приложений. Попрактикуйтесь, добавляя новые фичи в наше Todo-приложение!

1
Задача
Модуль 3: React, 2 уровень, 5 лекция
Недоступна
Создание редьюсера для управления состоянием
Создание редьюсера для управления состоянием
1
Задача
Модуль 3: React, 2 уровень, 5 лекция
Недоступна
Создание контекста с состоянием
Создание контекста с состоянием
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ