Подключение компонента к 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;
Объяснение:
useSelectorпринимает функцию, которая получаетstateиз Store. Это позволяет "вытащить" только те данные, которые нужны компоненту.- Тип
RootState(можно импортировать изstore.ts) позволяет сделать хук строго типизированным и предотвращает ошибки. - Данные из 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;
Объяснение:
- Хук
useDispatchвозвращает функциюdispatch, которая позволяет отправлять действия в Store. - Мы использовали действия
incrementиdecrement, которые были автоматически созданы вcounterSlice. - Передаём эти действия в
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
- Оптимизация рендеров.
useSelectorвызывает рендеринг компонента при любом изменении выбранного кусочка состояния. Если возможны лишние рендеры, оптимизируйте выборку данных (state.counterвместоstate). - Храните только необходимое в Store. Не используйте Redux Store для временного состояния. Например, состояние модального окна или значение инпута (для таких целей лучше оставить локальное состояние через
useState). - Не перегружайте 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);
Практическое задание
- Создайте новый компонент, который отображает список задач (todos) из Redux Store.
- Реализуйте добавление и удаление задач через
useDispatchи соответствующие действия. - Убедитесь, что ваш компонент типизирован правильно.
Пример структуры данных для состояния:
interface Todo {
id: number;
title: string;
completed: boolean;
}
interface TodoState {
todos: Todo[];
}
Подсказка: для изменения списка задач используйте createSlice.
Вот так, шаг за шагом, мы интегрировали Redux Toolkit с компонентами React. К этому моменту ваши компоненты уже начинают "общаться" с глобальным состоянием без лишних забот. Это позволяет создавать гибкие, мощные и масштабируемые приложения.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ