JavaRush /Курсы /Модуль 3: React /Интеграция Redux Toolkit с компонентами React

Интеграция Redux Toolkit с компонентами React

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

Подключение компонента к Redux Store

На данный момент у вас есть состояние в Redux Store, но, как великая истина гласит, "если дерево в лесу упало, и никто это не видел, то упало ли оно вообще?". Аналогично, какой смысл от нашего глобального состояния, если React-компоненты не могут его использовать? Сегодня мы научимся, как компоненты могут читать из глобального состояния и обновлять его, отправляя действия (actions).

Работа с useSelector для чтения данных

Для начала нужно овладеть таким навыком как чтение данных из Redux Store внутри компонента. Здесь нам на помощь приходит хук useSelector.

// src/components/Counter.tsx
import React from "react";
import { useSelector } from "react-redux";
import { RootState } from "../store";

const Counter: React.FC = () => {
  // Используем useSelector для получения данных из Store
  const count = useSelector((state: RootState) => state.counter.value);

  return (
    <div>
      <h1>Counter: {count}</h1>
    </div>
  );
};

export default Counter;

Объяснение:

  1. useSelector принимает функцию, которая получает state из Store. Это позволяет "вытащить" только те данные, которые нужны компоненту.
  2. Тип RootState (можно импортировать из store.ts) позволяет сделать хук строго типизированным и предотвращает ошибки.
  3. Данные из Store state.counter.value отображаются в нашем интерфейсе.

Работа с useDispatch для обновления состояния

Хорошо, данные мы уже умеем "читать". Но что, если мы хотим изменить их? Например, добавить единичку к счётчику? Для этого в Redux Toolkit используется функция dispatch, которую предоставляет хук useDispatch.

// src/components/Counter.tsx
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { RootState } from "../store";
import { increment, decrement } from "../slices/counterSlice";

const Counter: React.FC = () => {
  const count = useSelector((state: RootState) => state.counter.value);
  const dispatch = useDispatch();

  return (
    <div>
      <h1>Counter: {count}</h1>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
    </div>
  );
};

export default Counter;

Объяснение:

  1. Хук useDispatch возвращает функцию dispatch, которая позволяет отправлять действия в Store.
  2. Мы использовали действия increment и decrement, которые были автоматически созданы в counterSlice.
  3. Передаём эти действия в dispatch(), чтобы они вызвали соответствующие изменения в состоянии.

Типизация компонентов

Redux Toolkit славится своей интеграцией с TypeScript. Типизация позволяет создать безопасный и читаемый код, где ошибка сразу заметна.

Типизация useSelector

Чтобы воспользоваться преимуществами TypeScript, мы определяем тип состояния в Store (это уже есть в store.ts):

// src/store/index.ts
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "../slices/counterSlice";

export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});

// Определяем тип RootState, чтобы использовать в useSelector
export type RootState = ReturnType<typeof store.getState>;

После этого передаём RootState в useSelector:

const count = useSelector((state: RootState) => state.counter.value);

Типизация useDispatch

Redux Toolkit также предоставляет типизированный useDispatch:

import { store } from "./store";

// Определяем тип Dispatch:
export type AppDispatch = typeof store.dispatch;

В компоненте его можно использовать так:

import { useDispatch } from "react-redux";
import { AppDispatch } from "../store";

const dispatch = useDispatch<AppDispatch>();

Углубимся в интеграцию: сложные сценарии

Передача пропсов и взаимодействие

Иногда один компонент может одновременно получать данные как из пропсов, так и из Redux Store. Это обычная практика, так как не все данные нужно хранить в глобальном состоянии.

// src/components/CounterWithProps.tsx
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { RootState } from "../store";
import { increment } from "../slices/counterSlice";

interface CounterWithPropsProps {
  title: string;
}

const CounterWithProps: React.FC<CounterWithPropsProps> = ({ title }) => {
  const count = useSelector((state: RootState) => state.counter.value);
  const dispatch = useDispatch();

  return (
    <div>
      <h2>{title}</h2>
      <h1>Counter: {count}</h1>
      <button onClick={() => dispatch(increment())}>+</button>
    </div>
  );
};

export default CounterWithProps;

Теперь в родительском компоненте можно легко передать заголовок:

// src/App.tsx
import React from "react";
import CounterWithProps from "./components/CounterWithProps";

const App: React.FC = () => {
  return (
    <div>
      <CounterWithProps title="My Awesome Counter" />
    </div>
  );
};

export default App;

Советы и практики для работы с useSelector и useDispatch

  1. Оптимизация рендеров. useSelector вызывает рендеринг компонента при любом изменении выбранного кусочка состояния. Если возможны лишние рендеры, оптимизируйте выборку данных (state.counter вместо state).
  2. Храните только необходимое в Store. Не используйте Redux Store для временного состояния. Например, состояние модального окна или значение инпута (для таких целей лучше оставить локальное состояние через useState).
  3. Не перегружайте Store. В глобальном состоянии стоит хранить только действительно глобальные вещи, такие как пользовательские данные или кэшированные результаты.

Разбор кейсов

Кейс 1: несоответствие типов в useSelector

Ошибка:

const count = useSelector((state) => state.counter.value);
// Ошибка: TypeScript не знает, какой тип у state

Решение:

Убедитесь, что RootState передан в useSelector:

const count = useSelector((state: RootState) => state.counter.value);

Кейс 2: лишние рендеры

Если компонент слишком часто перерендеривается, проверьте, точно ли вы извлекаете только необходимое состояние:

// Плохо:
const entireState = useSelector((state: RootState) => state);
// Лишний рендер при любом изменении

// Хорошо:
const count = useSelector((state: RootState) => state.counter.value);

Практическое задание

  1. Создайте новый компонент, который отображает список задач (todos) из Redux Store.
  2. Реализуйте добавление и удаление задач через useDispatch и соответствующие действия.
  3. Убедитесь, что ваш компонент типизирован правильно.

Пример структуры данных для состояния:

interface Todo {
  id: number;
  title: string;
  completed: boolean;
}

interface TodoState {
  todos: Todo[];
}

Подсказка: для изменения списка задач используйте createSlice.

Вот так, шаг за шагом, мы интегрировали Redux Toolkit с компонентами React. К этому моменту ваши компоненты уже начинают "общаться" с глобальным состоянием без лишних забот. Это позволяет создавать гибкие, мощные и масштабируемые приложения.

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