JavaRush /Курсы /Модуль 3: React /Работа с dispatch и getState в Redux Thunk

Работа с dispatch и getState в Redux Thunk

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

Что такое 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());
  };
};

Общие ошибки и как их избежать

  1. Дублирующая логика: используйте getState, чтобы проверять состояние, перед тем как выполнять действия.
  2. Ошибки типизации: убедитесь, что все действия и состояние правильно типизированы с TypeScript.
  3. Бесконечные циклы загрузки: не забывайте завершать запросы и обновлять состояние после их выполнения.

Теперь, владея знаниями об dispatch и getState, вы сможете создавать более мощные и адаптивные Thunk-функции, которые работают на вашем надежном Redux-магазине, как на идеально смазанной машине.

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