React

Redux 的原理和工作中常用方式

这篇笔记记录一下我对 Redux 的理解。 我刚开始学 Redux 时,最困惑的是它看起来很绕: 后来写多了以后发现,Redux 不是为了让简单事情变复杂,而是为了让状态变化变得可追踪。 它最核心的思路可以压缩成一句话: Redux 解决什么问题 React 组件里可以直接用 useState。 小项目这样写完全没问题

2026年6月13日7 分钟阅读
Redux 的原理和工作中常用方式

这篇笔记记录一下我对 Redux 的理解。

我刚开始学 Redux 时,最困惑的是它看起来很绕:

text
改一个状态,为什么不能直接改?
为什么要 dispatch 一个 action?
为什么还要 reducer?

后来写多了以后发现,Redux 不是为了让简单事情变复杂,而是为了让状态变化变得可追踪。

它最核心的思路可以压缩成一句话:

text
用 action 描述发生了什么,用 reducer 根据旧状态计算新状态。

Redux 解决什么问题

React 组件里可以直接用 useState

小项目这样写完全没问题:

tsx
const [count, setCount] = useState(0);

但项目变大后,有些状态会被很多地方使用:

  • 当前登录用户。
  • 权限信息。
  • 主题配置。
  • 多页面共享的筛选条件。
  • 复杂表单数据。
  • 购物车。
  • 编辑器状态。

如果这些状态分散在各个组件里,问题会变多:

text
谁改了这个状态?
为什么这个页面也变了?
这个状态到底从哪里来?
多个组件要同步怎么办?

Redux 的目标就是把共享状态集中管理,并让每次修改都有明确记录。

Redux 的三个核心概念

最基础的 Redux 有三个概念:

text
store
action
reducer

Store

Store 是保存全局状态的地方。

可以粗略理解成:

ts
const state = store.getState();

整个应用需要共享的状态,都可以从这里读。

Action

Action 是一个普通对象,用来描述发生了什么。

例如:

ts
{
  type: "counter/increment"
}

或者:

ts
{
  type: "user/setName",
  payload: "小明"
}

Action 不负责修改状态。

它只是描述事件。

Reducer

Reducer 根据旧状态和 action,返回新状态。

ts
function counterReducer(state = { value: 0 }, action) {
  if (action.type === "counter/increment") {
    return {
      value: state.value + 1,
    };
  }

  return state;
}

Reducer 应该是纯函数。

同样的 stateaction,应该得到同样的新状态。

一次状态更新的流程

Redux 的数据流是单向的。

text
UI 触发事件
  -> dispatch(action)
  -> reducer 计算新 state
  -> store 保存新 state
  -> UI 根据新 state 重新渲染

例如点击按钮:

tsx
dispatch({ type: "counter/increment" });

Redux 会把 action 交给 reducer:

ts
newState = reducer(oldState, action);

然后 React 组件通过 selector 读到新状态。

这个流程虽然比直接 setState 多了几步,但好处是非常清楚:

text
每次变化都由一个 action 触发。

为什么不能直接修改 state

Redux 强调不可变更新。

不要这样写:

ts
state.value += 1;
return state;

而是返回新对象:

ts
return {
  ...state,
  value: state.value + 1,
};

原因是 React 和 Redux 都需要通过引用变化判断状态是否更新。

如果直接改原对象,外面可能看不出引用变了。

不过现在常用 Redux Toolkit,它内部使用 Immer,可以让我们写出看起来像直接修改的代码:

ts
increment(state) {
  state.value += 1;
}

Immer 会帮我们生成不可变的新状态。

工作中更常用 Redux Toolkit

现在不太推荐从零手写大量 Redux 模板代码。

更常用的是 Redux Toolkit。

一个 slice 大概长这样:

ts
import { createSlice } from "@reduxjs/toolkit";

const counterSlice = createSlice({
  name: "counter",
  initialState: {
    value: 0,
  },
  reducers: {
    increment(state) {
      state.value += 1;
    },
    add(state, action) {
      state.value += action.payload;
    },
  },
});

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

使用时:

tsx
const value = useSelector((state) => state.counter.value);
const dispatch = useDispatch();

return (
  <button onClick={() => dispatch(increment())}>
    {value}
  </button>
);

Redux Toolkit 帮我们减少了很多重复代码:

  • 自动生成 action creator。
  • 自动处理不可变更新。
  • 简化 store 配置。
  • 默认集成常用中间件。

异步请求怎么处理

Redux 的 reducer 不能直接写异步逻辑。

因为 reducer 应该是纯函数。

异步请求通常放在 thunk 里。

Redux Toolkit 提供了 createAsyncThunk

ts
export const fetchUser = createAsyncThunk(
  "user/fetchUser",
  async (userId: string) => {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  },
);

slice 里处理不同状态:

ts
extraReducers: (builder) => {
  builder
    .addCase(fetchUser.pending, (state) => {
      state.loading = true;
    })
    .addCase(fetchUser.fulfilled, (state, action) => {
      state.loading = false;
      state.data = action.payload;
    })
    .addCase(fetchUser.rejected, (state) => {
      state.loading = false;
      state.error = "加载失败";
    });
}

这样请求过程也会变成清晰的状态变化:

text
pending
fulfilled
rejected

Selector 的作用

组件不一定要直接知道完整 state 结构。

可以用 selector 封装读取逻辑:

ts
export const selectCurrentUser = (state: RootState) => {
  return state.user.current;
};

组件里:

tsx
const user = useSelector(selectCurrentUser);

如果读取逻辑复杂,还可以做派生计算。

Selector 的好处是:

  • 组件更干净。
  • state 结构调整时影响范围更小。
  • 复杂计算可以集中管理。

哪些状态适合放 Redux

不是所有状态都要放 Redux。

适合放 Redux 的状态通常有这些特征:

  • 多个页面或组件共享。
  • 需要跨路由保留。
  • 状态变化需要追踪。
  • 和业务身份、权限、配置有关。
  • 多个操作会影响同一份数据。

不太适合放 Redux 的状态:

  • 输入框临时内容。
  • 弹窗开关。
  • 鼠标悬停。
  • 某个组件内部的小状态。

这些用 useState 或自定义 Hook 更简单。

Redux 不是状态越多越好,而是共享状态需要有秩序。

我最后想通的地方

Redux 的核心不是“全局变量仓库”。

如果只是为了全局能读写,随便一个对象也能做到。

Redux 真正强调的是:

text
状态集中保存
变化通过 action 描述
reducer 负责计算新状态
UI 根据状态重新渲染

它让状态变化有路径、有记录、有边界。

在工作中,我会优先使用 Redux Toolkit,而不是手写老式 Redux 模板。

同时也会克制使用:

text
局部状态留在组件里
共享业务状态再放 Redux
异步请求和派生数据有清晰边界

这样 Redux 才是在帮项目变清楚,而不是把所有简单状态都绕一圈。