← Назад к вопросам

Как работает поток данных между Redux и компонентом?

1.8 Middle🔥 192 комментариев
#State Management

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работает поток данных между Redux и компонентом

Redux — это библиотека управления состоянием с однонаправленным потоком данных. Понимание этого потока критично для правильного использования Redux.

1. Redux архитектура (однонаправленный поток)

Компонент
   |
   v (dispatch action)
Action Creator
   |
   v (returns action object)
Reducer
   |
   v (returns new state)
Store (состояние)
   |
   v (subscribe/notify)
Компонент (re-render)

2. Store - источник истины

Store содержит всё состояние приложения:

// Store это один большой объект
const store = {
  users: [
    { id: 1, name: 'Иван' },
    { id: 2, name: 'Мария' }
  ],
  ui: {
    isLoading: false,
    theme: 'dark'
  },
  auth: {
    isLoggedIn: true,
    token: 'abc123'
  }
};

// Komponenty читают из store через селекторы
const userName = store.users[0].name;
const isLoading = store.ui.isLoading;

3. Actions - описание изменений

Action это простой объект, описывающий что должно измениться:

// Action это объект с type
const addUserAction = {
  type: 'ADD_USER',
  payload: { name: 'Петр', email: 'petr@example.com' }
};

// Action Creator возвращает Action
const addUser = (name, email) => ({
  type: 'ADD_USER',
  payload: { name, email }
});

// Или с Redux Toolkit (современный подход)
import { createAction } from '@reduxjs/toolkit';
const addUser = createAction('users/add');

4. Reducers - чистые функции для обновления состояния

Reducer принимает текущее состояние и action, возвращает новое состояние:

// Чистая функция: тот же input => тот же output
const userReducer = (state = [], action) => {
  switch (action.type) {
    case 'ADD_USER':
      // ВАЖНО: не мутируй состояние, создавай новое
      return [
        ...state,
        { id: state.length + 1, ...action.payload }
      ];
    
    case 'REMOVE_USER':
      return state.filter(user => user.id !== action.payload);
    
    case 'UPDATE_USER':
      return state.map(user =>
        user.id === action.payload.id
          ? { ...user, ...action.payload }
          : user
      );
    
    default:
      return state;
  }
};

// Redux Toolkit (рекомендуется)
import { createSlice } from '@reduxjs/toolkit';

const userSlice = createSlice({
  name: 'users',
  initialState: [],
  reducers: {
    addUser: (state, action) => {
      // RTK использует Immer, можно мутировать
      state.push({ id: state.length + 1, ...action.payload });
    },
    removeUser: (state, action) => {
      return state.filter(user => user.id !== action.payload);
    }
  }
});

5. Поток данных: Dispatch

Компонент dispatch action, что запускает цепочку:

// 1. Компонент вызывает dispatch
const MyComponent = () => {
  const dispatch = useDispatch();
  
  const handleAddUser = () => {
    // Отправляем action в reducer
    dispatch(addUser('Петр', 'petr@example.com'));
  };
  
  return <button onClick={handleAddUser}>Добавить пользователя</button>;
};

// 2. Reducer получает action и обновляет state
// (происходит автоматически)

// 3. Store уведомляет компоненты об изменении
// (происходит автоматически)

// 4. Компонент перерисовывается с новыми данными

6. Поток данных: Select (Selectors)

Компоненты читают данные из Store через селекторы:

// Селектор - функция, которая берет данные из состояния
const selectUsers = (state) => state.users;
const selectUserCount = (state) => state.users.length;
const selectUserById = (state, id) => 
  state.users.find(user => user.id === id);

// В компоненте используем useSelector
const MyComponent = () => {
  // useSelector подписывает компонент на изменения
  const users = useSelector(selectUsers);
  const userCount = useSelector(selectUserCount);
  
  // Компонент перерисуется только если users или userCount изменилась
  // (shallow comparison)
  
  return (
    <div>
      <p>Всего пользователей: {userCount}</p>
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
};

7. Полный цикл данных

// Инициальное состояние
const initialState = {
  users: [{ id: 1, name: 'Иван' }],
  ui: { isLoading: false }
};

// 1. Компонент просит данные
const UserList = () => {
  // useSelector подписывается на изменения
  const users = useSelector(state => state.users);
  const isLoading = useSelector(state => state.ui.isLoading);
  const dispatch = useDispatch();
  
  useEffect(() => {
    // 2. Компонент говорит: "нужны пользователи"
    dispatch({ type: 'FETCH_USERS_START' });
    
    // 3. Reducer обновляет isLoading = true
    // 4. Store уведомляет компонент
    // 5. Компонент перерисуется и покажет spinner
    
    // Затем загружаем данные
    fetch('/api/users')
      .then(res => res.json())
      .then(data => {
        // 6. Dispatch action с данными
        dispatch({ type: 'FETCH_USERS_SUCCESS', payload: data });
        // 7. Reducer обновляет users и isLoading = false
        // 8. Store уведомляет компонент
        // 9. Компонент перерисуется с новыми данными
      })
      .catch(error => {
        dispatch({ type: 'FETCH_USERS_ERROR', payload: error });
      });
  }, [dispatch]);
  
  if (isLoading) return <p>Загрузка...</p>;
  
  return (
    <ul>
      {users.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  );
};

8. Middleware (побочные эффекты)

Для асинхронных операций используй middleware:

// Redux Thunk - функция вместо объекта
const fetchUsers = () => async (dispatch) => {
  dispatch({ type: 'FETCH_START' });
  
  try {
    const response = await fetch('/api/users');
    const data = await response.json();
    
    // Теперь можешь dispatch несколько actions
    dispatch({ type: 'FETCH_SUCCESS', payload: data });
  } catch (error) {
    dispatch({ type: 'FETCH_ERROR', payload: error });
  }
};

// В компоненте
const MyComponent = () => {
  const dispatch = useDispatch();
  
  useEffect(() => {
    // Dispatch возвращает promise
    dispatch(fetchUsers()).then(() => {
      console.log('Done!');
    });
  }, [dispatch]);
};

// Redux Toolkit Query (рекомендуется для API)
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

const usersApi = createApi({
  reducerPath: 'usersApi',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  endpoints: (builder) => ({
    getUsers: builder.query({
      query: () => '/users'
    }),
    addUser: builder.mutation({
      query: (user) => ({
        url: '/users',
        method: 'POST',
        body: user
      })
    })
  })
});

// В компоненте
const { data: users, isLoading } = usersApi.useGetUsersQuery();
const [addUser] = usersApi.useAddUserMutation();

9. Оптимизация: Memoization селекторов

import { createSelector } from '@reduxjs/toolkit';

// Базовый селектор
const selectUsers = (state) => state.users;
const selectUserId = (state, id) => id;

// Мемоизированный селектор - пересчитывается только если users изменилась
const selectUserById = createSelector(
  [selectUsers, selectUserId],
  (users, id) => users.find(user => user.id === id)
);

// useSelector с мемоизированным селектором не вызывает лишние re-render
const user = useSelector(state => selectUserById(state, userId));

Итоговая схема потока

Компонент
  |
  | dispatch(action)
  v
Store → Reducer → NewState
  |
  | subscribe: state changed!
  v
useSelector
  |
  | Shallow comparison
  v
Перерендер компонента (если данные изменились)

Этот однонаправленный поток делает Redux предсказуемым и легко отлаживаемым. Данные всегда течут в одну сторону: action -> reducer -> store -> component.

Как работает поток данных между Redux и компонентом? | PrepBro