Основы использования useSelector
Итак, представьте, что у нас есть огромный холодильник с данными (Redux Store). Если мы хотим что-то из него достать, мы используем useSelector. Это наш пропуск к состоянию, хранящемуся в этом холодильнике.
Как работает useSelector?
useSelector — это хук, который позволяет извлекать данные из Redux Store в любом компоненте React. Мы передаём в него функцию-селектор, которая выбирает часть состояния, нужную компоненту.
Вот пример:
import { useSelector } from 'react-redux';
import { RootState } from './store';
const MyComponent = () => {
// Извлекаем часть состояния из Store
const count = useSelector((state: RootState) => state.counter.value);
return (
<div>
<p>Текущее значение: {count}</p>
</div>
);
};
Здесь:
state: RootState— это весь Store, типизированный вашим интерфейсом. Мы заранее описали структуру в предыдущих лекциях.(state: RootState) => state.counter.value— селектор, который лезет в хранилище и достаёт значениеvalueиз срезаcounter.
Почему useSelector важен?
- Минимизация ререндера. Компонент перерисовывается только при изменении данных, которые он выбирает.
- Локальное управление состоянием. Вы используете только те данные, которые нужны этому компоненту.
Типизация useSelector
TypeScript делает нас не только счастливыми, но и защищёнными от кучи глупых ошибок. Чтобы типизация в useSelector работала как часы, мы заранее описываем структуру состояния с помощью интерфейсов.
// Интерфейс для состояния
interface CounterState {
value: number;
}
// Интеграция состояния с Slice
const initialState: CounterState = {
value: 0,
};
const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
},
});
export const { increment, decrement } = counterSlice.actions;
// Типизация RootState
export interface RootState {
counter: CounterState;
}
Теперь при использовании useSelector мы передаём RootState как тип для всего состояния, что обеспечит автодополнение и проверку типов на этапе написания кода.
Основы использования useDispatch
Если useSelector — это наш холодильник, то useDispatch — это кнопка вызова доставки продуктов к вам на стол. С его помощью мы отправляем действия (actions) в Store, чтобы изменить состояние.
Как работает useDispatch?
useDispatch возвращает функцию, которая позволяет отправлять действия на обработку редьюсером. Вот пример:
import { useDispatch } from 'react-redux';
import { increment, decrement } from './counterSlice';
const MyComponent = () => {
const dispatch = useDispatch();
return (
<div>
<button onClick={() => dispatch(increment())}>Увеличить</button>
<button onClick={() => dispatch(decrement())}>Уменьшить</button>
</div>
);
};
Как видите, мы просто вызываем dispatch, передавая ему действие, которое заранее определено в нашем срезе.
Типизация useDispatch
Да, друзья, TypeScript здесь тоже приходит на помощь. Чтобы наш dispatch знал, какие действия он может принимать, нам нужно немного магии типизации.
Пример типизации:
import { AppDispatch } from './store';
import { useDispatch } from 'react-redux';
// Создаем свою версию useDispatch с типами
export const useTypedDispatch = () => useDispatch<AppDispatch>();
Теперь вы можете использовать свой типизированный хук useTypedDispatch вместо стандартного useDispatch.
Советы и рекомендации: избегаем лишних рендеров
Одной из типичных проблем при работе с useSelector является то, что компоненты могут перерисовываться чаще, чем нужно. Это происходит, если селектор возвращает ссылочные типы (например, массивы или объекты), которые создаются заново при каждом обновлении.
Пример проблемы:
const MyComponent = () => {
const items = useSelector((state: RootState) => state.items); // Вернётся новый объект каждый раз!
return (
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
};
Чтобы минимизировать ошибки, вы можете использовать библиотеку reselect для создания мемоизированных селекторов. Это позволяет избежать повторного создания массивов и объектов.
Практический пример: приложение счетчика
Давайте напишем простое приложение, которое использует useSelector и useDispatch.
Компонент Counter:
import React from 'react';
import { useSelector } from 'react-redux';
import { useTypedDispatch } from './store';
import { increment, decrement } from './counterSlice';
export const Counter = () => {
const count = useSelector((state: RootState) => state.counter.value);
const dispatch = useTypedDispatch();
return (
<div>
<h1>Счётчик: {count}</h1>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
</div>
);
};
Обратите внимание, как лаконично выглядит код с типизацией. Никаких магических строк, всё строго по интерфейсам.
Тестирование в консоли
Запустите приложение и попробуйте нажимать кнопки. Проверьте, как изменяется состояние в Redux DevTools (если вы подключили его раньше). Если всё сделано правильно, вы должны увидеть обновление состояния при каждом нажатии!
Полезные советы:
- Старайтесь извлекать только те данные, которые необходимы для конкретного компонента.
- Используйте типизацию для селекторов и
dispatch, чтобы избежать непредсказуемого поведения. - Помните про оптимизацию рендеров, особенно при работе с большими приложениями и множеством компонентов.
Поздравляю! Теперь вы умеете извлекать данные из Store и отправлять действия для их изменения.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ