说说你对 Redux 的理解?其工作原理?

一、是什么

Redux 是一个可预测的状态管理容器,用于 JavaScript 应用。它将应用的全部状态存储在一个单一的 store 中,通过纯函数(reducer)来描述状态如何响应 action 进行变化。

Redux 的灵感来源于 Flux 架构和 Elm 语言,核心理念是让状态变化可追踪、可调试、可预测。

用户操作 → dispatch(action) → reducer(state, action) → newState → UI 更新

二、核心概念

Store — 唯一的状态容器

整个应用只有一个 store,它持有完整的状态树:

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

const store = configureStore({
  reducer: {
    counter: counterReducer,
    todos: todosReducer,
    user: userReducer,
  },
});

// 获取当前状态
console.log(store.getState());
// { counter: { value: 0 }, todos: [], user: null }

Action — 状态变化的描述

Action 是一个普通 JavaScript 对象,必须包含 type 字段,描述"发生了什么":

// 手动定义 action
const increment = { type: 'counter/increment' };
const addTodo = { type: 'todos/add', payload: { text: '学 Redux', id: 1 } };

// 使用 action creator 函数
function incrementBy(amount) {
  return { type: 'counter/incrementBy', payload: amount };
}

Reducer — 纯函数状态计算

Reducer 接收当前 state 和 action,返回新的 state。它必须是纯函数——不能修改参数、不能有副作用、不能调用不纯的函数:

function counterReducer(state = { value: 0 }, action) {
  switch (action.type) {
    case 'counter/increment':
      return { ...state, value: state.value + 1 };
    case 'counter/decrement':
      return { ...state, value: state.value - 1 };
    case 'counter/incrementBy':
      return { ...state, value: state.value + action.payload };
    default:
      return state;
  }
}

Dispatch — 触发状态变化

dispatch 是唯一触发状态变化的方式:

store.dispatch({ type: 'counter/increment' });
store.dispatch({ type: 'counter/incrementBy', payload: 5 });

三、三大原则

1. 单一数据源(Single Source of Truth)

整个应用的状态存储在一个 store 的状态树中。这使得状态的调试和序列化变得简单——你可以把整个应用状态导出为 JSON,也可以从 JSON 恢复:

// 状态持久化
localStorage.setItem('appState', JSON.stringify(store.getState()));

// 状态恢复
const preloadedState = JSON.parse(localStorage.getItem('appState'));
const store = configureStore({ reducer: rootReducer, preloadedState });

2. State 是只读的(Read-Only State)

修改 state 的唯一方式是 dispatch 一个 action。不能直接 state.count = 1。这保证了所有状态变化都有据可查。

3. 使用纯函数执行变化(Pure Reducers)

Reducer 是纯函数,给定相同的输入永远返回相同的输出。这使得:

  • 状态变化可预测
  • 方便单元测试
  • 支持时间旅行调试
// 纯函数 reducer
function todosReducer(state = [], action) {
  switch (action.type) {
    case 'todos/add':
      return [...state, action.payload]; // 返回新数组,不修改原数组
    case 'todos/toggle':
      return state.map(todo =>
        todo.id === action.payload
          ? { ...todo, done: !todo.done } // 返回新对象
          : todo
      );
    case 'todos/remove':
      return state.filter(todo => todo.id !== action.payload);
    default:
      return state;
  }
}

四、数据流

Redux 采用严格的单向数据流

           ┌─────────────────────────────────────┐
           │              Store                   │
           │  ┌───────────────────────────────┐   │
           │  │         State Tree            │   │
           │  │  { counter: 0, todos: [] }    │   │
           │  └───────────────────────────────┘   │
           └──────┬───────────────────┬───────────┘
                  │                   │
          getState()           subscribe()
                  │                   │
                  ▼                   ▼
           ┌──────────┐      ┌──────────────┐
           │   View   │      │   Listener   │
           │  (React) │      │  (re-render) │
           └────┬─────┘      └──────────────┘

          User Interaction


         dispatch(action)


           ┌──────────┐
           │ Reducer   │
           │ (state,   │──→ newState → Store 更新
           │  action)  │
           └──────────┘

完整流程:

  1. 用户在 UI 上触发操作(点击按钮、提交表单等)
  2. 组件调用 dispatch(action) 发送一个 action
  3. Store 将当前 state 和 action 传给 reducer
  4. Reducer 计算并返回新的 state
  5. Store 保存新 state,通知所有订阅者
  6. React 组件读取新 state,触发重渲染

五、Redux Toolkit — 现代 Redux

原始 Redux 存在大量样板代码(boilerplate),Redux Toolkit(RTK)是官方推荐的编写 Redux 的方式,极大简化了开发体验。

createSlice

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

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment(state) {
      state.value += 1; // RTK 内部使用 Immer,可以"直接修改"
    },
    decrement(state) {
      state.value -= 1;
    },
    incrementBy(state, action) {
      state.value += action.payload;
    },
  },
});

export const { increment, decrement, incrementBy } = counterSlice.actions;
export default counterSlice.reducer;

RTK 的 createSlice 自动生成 action creators 和 action type 字符串,内部集成了 Immer 库,允许在 reducer 中编写"看起来像直接修改"的代码,实际上产生的是不可变更新。

configureStore

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
import todosReducer from './todosSlice';

const store = configureStore({
  reducer: {
    counter: counterReducer,
    todos: todosReducer,
  },
  // 自动配置了 redux-thunk 中间件和 Redux DevTools
});

export default store;

异步操作:createAsyncThunk

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

export const fetchUsers = createAsyncThunk(
  'users/fetchAll',
  async (_, { rejectWithValue }) => {
    try {
      const response = await fetch('/api/users');
      if (!response.ok) throw new Error('Network error');
      return await response.json();
    } catch (err) {
      return rejectWithValue(err.message);
    }
  }
);

const usersSlice = createSlice({
  name: 'users',
  initialState: { list: [], loading: false, error: null },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchUsers.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchUsers.fulfilled, (state, action) => {
        state.loading = false;
        state.list = action.payload;
      })
      .addCase(fetchUsers.rejected, (state, action) => {
        state.loading = false;
        state.error = action.payload;
      });
  },
});

六、什么时候适合用 Redux

适合的场景

  • 大量应用状态需要在多个组件间共享
  • 状态更新逻辑复杂,涉及多个相互关联的数据
  • 团队协作需要规范化的状态管理模式
  • 需要强大的调试能力(DevTools 时间旅行)
  • 需要中间件处理复杂的副作用流程

不适合的场景

  • 小型应用,useState + useContext 就够了
  • 状态只在少数几个相邻组件间传递
  • 原型阶段,快速迭代优先
  • 团队对 Redux 不熟悉且项目周期紧张

七、总结

Redux 通过单一 store、action 驱动、纯函数 reducer 的模式,让状态管理变得可预测、可追踪。Redux Toolkit 解决了原始 Redux 的冗余样板代码问题,是现代 React 项目使用 Redux 的标准方式。

核心要点:

  • Store 是全局唯一的状态容器
  • Action 描述发生了什么
  • Reducer 是纯函数,决定状态怎么变
  • Dispatch 是触发变化的唯一途径
  • RTK 让 Redux 开发简洁高效