Что такое dispatch и getState?
Если Redux был бы почтой, dispatch — вашим почтовым курьером, который доставляет письма (действия) в нужную точку (редьюсер). А getState — это как справочник, к которому вы обращаетесь, чтобы узнать, в каком состоянии находится ваш адресат (глобальное состояние).
Основная роль dispatch:
- Отправка действий (action) в редьюсер, который обновляет состояние (state).
- Поддержка работы как синхронных, так и асинхронных действий.
Основная роль getState:
- Получение текущего состояния хранилища (store) в любой момент.
- Используется для проверки состояния перед выполнением действий (например, перед запросом к API).
Как использовать dispatch и getState в Thunk?
Thunk-функция в Redux принимает dispatch и getState как параметры. Используя их, мы можем гибко управлять состоянием и отправкой действий. Вот небольшой пример:
import { ThunkAction } from 'redux-thunk';
import { AnyAction } from 'redux';
import { RootState } from '../store';
// Пример Thunk-функции
export const fetchData = (): ThunkAction<void, RootState, unknown, AnyAction> => {
return async (dispatch, getState) => {
try {
// Отправляем действие, чтобы показать индикатор загрузки
dispatch({ type: 'FETCH_DATA_REQUEST' });
// Получаем состояние
const state = getState();
console.log('Текущее состояние:', state);
// Выполняем API-запрос
const response = await fetch('https://api.example.com/data');
const data = await response.json();
// Отправляем данные в редьюсер
dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data });
} catch (error) {
// Обрабатываем ошибки
dispatch({ type: 'FETCH_DATA_FAILURE', payload: error.message });
}
};
};
В этом примере dispatch используется для отправки действий в редьюсер на разных этапах запроса, а getState — для получения текущего состояния перед запросом.
Различия между синхронным и асинхронным использованием dispatch
Одной из мощных сторон Redux Thunk является возможность смешивать синхронные и асинхронные действия. Чтобы понять это, взглянем на разницу между синхронным и асинхронным использованием dispatch.
Синхронное использование:
Синхронные действия просто передаются редьюсеру мгновенно. Например, изменение состояния загрузки:
dispatch({ type: 'SET_LOADING', payload: true });
Редьюсер тут же обновит state.loading.
Асинхронное использование:
Асинхронные действия, напротив, выполнены позже, например, после получения данных от API:
dispatch(asyncAction());
Асинхронная функция сначала выполнит логику (например, запрос к серверу), а затем отправит несколько синхронных действий.
Практический пример: использование dispatch для работы с API
Давайте добавим немного практики. Представьте себе приложение для просмотра списка фильмов. Мы будем запрашивать данные с сервера, отображать их на странице и показывать пользователю индикатор загрузки.
Шаг 1: Создаём действия
// actions.ts
export const FETCH_MOVIES_REQUEST = 'FETCH_MOVIES_REQUEST';
export const FETCH_MOVIES_SUCCESS = 'FETCH_MOVIES_SUCCESS';
export const FETCH_MOVIES_FAILURE = 'FETCH_MOVIES_FAILURE';
export interface FetchMoviesRequestAction {
type: typeof FETCH_MOVIES_REQUEST;
}
export interface FetchMoviesSuccessAction {
type: typeof FETCH_MOVIES_SUCCESS;
payload: string[];
}
export interface FetchMoviesFailureAction {
type: typeof FETCH_MOVIES_FAILURE;
payload: string;
}
export type MovieActions =
| FetchMoviesRequestAction
| FetchMoviesSuccessAction
| FetchMoviesFailureAction;
Шаг 2: Создаём Thunk-функцию
// thunks.ts
import { ThunkAction } from 'redux-thunk';
import { RootState } from '../store';
import {
FETCH_MOVIES_REQUEST,
FETCH_MOVIES_SUCCESS,
FETCH_MOVIES_FAILURE,
MovieActions,
} from './actions';
export const fetchMovies = (): ThunkAction<void, RootState, unknown, MovieActions> => {
return async (dispatch, getState) => {
// Отправляем действие, чтобы показать индикатор загрузки
dispatch({ type: FETCH_MOVIES_REQUEST });
try {
const response = await fetch('https://api.example.com/movies');
const movies = await response.json();
// Отправляем успешное действие
dispatch({ type: FETCH_MOVIES_SUCCESS, payload: movies });
} catch (error) {
// Отправляем действие на случай ошибки
dispatch({ type: FETCH_MOVIES_FAILURE, payload: error.message });
}
};
};
Шаг 3: Обновляем редьюсер
Редьюсер обрабатывает наши действия:
// reducer.ts
import {
FETCH_MOVIES_REQUEST,
FETCH_MOVIES_SUCCESS,
FETCH_MOVIES_FAILURE,
MovieActions,
} from './actions';
export interface MovieState {
movies: string[];
isLoading: boolean;
error: string | null;
}
const initialState: MovieState = {
movies: [],
isLoading: false,
error: null,
};
export const movieReducer = (state = initialState, action: MovieActions): MovieState => {
switch (action.type) {
case FETCH_MOVIES_REQUEST:
return { ...state, isLoading: true, error: null };
case FETCH_MOVIES_SUCCESS:
return { ...state, isLoading: false, movies: action.payload };
case FETCH_MOVIES_FAILURE:
return { ...state, isLoading: false, error: action.payload };
default:
return state;
}
};
Шаг 4: Привязываем всё это к компонентам
Теперь мы можем вызывать fetchMovies из компонента:
// MoviesList.tsx
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchMovies } from './thunks';
import { RootState } from '../store';
const MoviesList: React.FC = () => {
const dispatch = useDispatch();
const { movies, isLoading, error } = useSelector((state: RootState) => state.movies);
useEffect(() => {
dispatch(fetchMovies());
}, [dispatch]);
if (isLoading) {
return <p>Загрузка...</p>;
}
if (error) {
return <p>Ошибка: {error}</p>;
}
return (
<ul>
{movies.map((movie, index) => (
<li key={index}>{movie}</li>
))}
</ul>
);
};
export default MoviesList;
Использование getState для проверки состояния
Иногда вам нужно проверить состояние перед выполнением действия. Например, не загружать данные, если они уже есть в хранилище:
export const fetchMoviesIfNeeded = (): ThunkAction<void, RootState, unknown, MovieActions> => {
return async (dispatch, getState) => {
const state = getState();
if (state.movies.length > 0) {
console.log('Фильмы уже загружены, повторный запрос не требуется.');
return;
}
dispatch(fetchMovies());
};
};
Общие ошибки и как их избежать
- Дублирующая логика: используйте
getState, чтобы проверять состояние, перед тем как выполнять действия. - Ошибки типизации: убедитесь, что все действия и состояние правильно типизированы с TypeScript.
- Бесконечные циклы загрузки: не забывайте завершать запросы и обновлять состояние после их выполнения.
Теперь, владея знаниями об dispatch и getState, вы сможете создавать более мощные и адаптивные Thunk-функции, которые работают на вашем надежном Redux-магазине, как на идеально смазанной машине.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ