React 状态管理演进:从 useState 到 Redux Toolkit

小飞兽 React 208 次阅读 2026-07-18

React 状态管理演进:从 useState 到 Redux Toolkit

一、Introduction

React 状态管理的复杂度取决于应用的规模。对于简单的单页应用,useState + useContext 的组合完全够用;但当应用扩展到几十个组件、多个数据来源、复杂的状态流转逻辑时,就需要更系统化的状态管理方案。

本文梳理 React 状态管理的演进路径:从基础的 useState,到抽象逻辑的 useReducer,再到 Context 共享状态,最终到 Redux Toolkit——每种方案的适用场景和取舍,帮助你在不同阶段选择合适的工具。

---

二、useState + useReducer:组件内部状态

#### 2.1 简单状态用 useState

单个组件内部的状态,直接用 useState。这是 React 状态管理的起点,也是大多数场景的答案。

import React, { useState } from 'react';

function LikeButton() {
  const [liked, setLiked] = useState(false);
  const [count, setCount] = useState(0);

  return (
    <button
      onClick={() => {
        setLiked(!liked);
        setCount(c => liked ? c - 1 : c + 1);
      }}
      style={{
        color: liked ? 'red' : 'gray',
        fontSize: 18
      }}
    >
      {liked ? '❤️' : '🤍'} {count}
    </button>
  );
}

#### 2.2 复杂状态用 useReducer

当组件内部有多个相关联的状态字段,且状态的更新逻辑复杂时,用 useReducer 集中管理:

import React, { useReducer } from 'react';

// 状态和 action 的定义清晰且集中
const initialState = {
  filter: 'all',     // 'all' | 'active' | 'completed'
  todos: [
    { id: 1, text: '学习 React', completed: false },
    { id: 2, text: '学习 TypeScript', completed: true },
    { id: 3, text: '完成项目', completed: false },
  ]
};

function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        todos: [
          ...state.todos,
          {
            id: Date.now(),
            text: action.payload,
            completed: false
          }
        ]
      };

    case 'TOGGLE_TODO':
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload
            ? { ...todo, completed: !todo.completed }
            : todo
        )
      };

    case 'DELETE_TODO':
      return {
        ...state,
        todos: state.todos.filter(todo => todo.id !== action.payload)
      };

    case 'SET_FILTER':
      return { ...state, filter: action.payload };

    case 'CLEAR_COMPLETED':
      return {
        ...state,
        todos: state.todos.filter(todo => !todo.completed)
      };

    default:
      return state;
  }
}

function TodoApp() {
  const [state, dispatch] = useReducer(todoReducer, initialState);
  const [inputText, setInputText] = React.useState('');

  const filteredTodos = state.todos.filter(todo => {
    if (state.filter === 'active') return !todo.completed;
    if (state.filter === 'completed') return todo.completed;
    return true;
  });

  const activeCount = state.todos.filter(t => !t.completed).length;

  return (
    <div style={{ maxWidth: 400 }}>
      <h2>待办事项({activeCount} 项待完成)</h2>

      <form
        onSubmit={e => {
          e.preventDefault();
          if (!inputText.trim()) return;
          dispatch({ type: 'ADD_TODO', payload: inputText });
          setInputText('');
        }}
      >
        <input
          value={inputText}
          onChange={e => setInputText(e.target.value)}
          placeholder="添加新待办..."
          style={{ width: '70%', padding: 8 }}
        />
        <button type="submit" style={{ padding: 8 }}>添加</button>
      </form>

      <ul style={{ listStyle: 'none', padding: 0 }}>
        {filteredTodos.map(todo => (
          <li
            key={todo.id}
            style={{
              padding: '8px 0',
              borderBottom: '1px solid #eee',
              display: 'flex',
              alignItems: 'center'
            }}
          >
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}
              style={{ marginRight: 8 }}
            />
            <span
              style={{
                flex: 1,
                textDecoration: todo.completed ? 'line-through' : 'none',
                color: todo.completed ? '#999' : '#333'
              }}
            >
              {todo.text}
            </span>
            <button
              onClick={() => dispatch({ type: 'DELETE_TODO', payload: todo.id })}
              style={{ color: 'red', border: 'none', background: 'none', cursor: 'pointer' }}
            >
              ×
            </button>
          </li>
        ))}
      </ul>

      <div style={{ marginTop: 12 }}>
        <span>筛选:</span>
        {['all', 'active', 'completed'].map(f => (
          <button
            key={f}
            onClick={() => dispatch({ type: 'SET_FILTER', payload: f })}
            disabled={state.filter === f}
            style={{ marginLeft: 4 }}
          >
            {f === 'all' ? '全部' : f === 'active' ? '进行中' : '已完成'}
          </button>
        ))}
        <button
          onClick={() => dispatch({ type: 'CLEAR_COMPLETED' })}
          style={{ marginLeft: 12, color: 'red' }}
        >
          清除已完成
        </button>
      </div>
    </div>
  );
}

export default TodoApp;

---

三、Redux Toolkit:企业级状态管理

#### 3.1 为什么需要 Redux Toolkit?

原生 Redux(createStore + reducer + dispatch)的样板代码太多。Redux Toolkit(RTK)通过约定优于配置的方式,大幅简化了 Redux 的使用,同时保留了 Redux 的核心优势:可预测的状态、强大的 DevTools、丰富的中间件生态。

import React from 'react';
import { configureStore, createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { Provider, useDispatch, useSelector } from 'react-redux';

// ==================== Redux Slice 定义 ====================

// 创建异步 thunk:发起 API 请求
const fetchPosts = createAsyncThunk(
  'posts/fetchPosts',
  async (userId) => {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/posts?userId=${userId}`
    );
    if (!response.ok) throw new Error('网络错误');
    return response.json();
  }
);

// 创建 slice:包含状态初始值和 reducer
const postsSlice = createSlice({
  name: 'posts',
  initialState: {
    items: [],
    loading: false,
    error: null,
    selectedPostId: null
  },
  reducers: {
    selectPost: (state, action) => {
      state.selectedPostId = action.payload;
    },
    clearPosts: (state) => {
      state.items = [];
      state.selectedPostId = null;
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchPosts.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchPosts.fulfilled, (state, action) => {
        state.loading = false;
        state.items = action.payload;
      })
      .addCase(fetchPosts.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      });
  }
});

const { selectPost, clearPosts } = postsSlice.actions;

// ==================== Store 配置 ====================

const store = configureStore({
  reducer: {
    posts: postsSlice.reducer
  }
});

// ==================== React 组件 ====================

function ReduxPostsApp() {
  const dispatch = useDispatch();
  const { items, loading, error, selectedPostId } = useSelector(state => state.posts);

  const selectedPost = items.find(p => p.id === selectedPostId);

  return (
    <Provider store={store}>
      <div style={{ display: 'flex', gap: 20 }}>
        <div style={{ flex: 1 }}>
          <h3>文章列表(Redux Toolkit)</h3>

          <div style={{ marginBottom: 12 }}>
            {[1, 2, 3].map(userId => (
              <button
                key={userId}
                onClick={() => dispatch(fetchPosts(userId))}
                style={{ marginRight: 4 }}
              >
                加载用户 {userId} 的文章
              </button>
            ))}
            <button
              onClick={() => dispatch(clearPosts())}
              style={{ color: 'red' }}
            >
              清空
            </button>
          </div>

          {loading && <p>加载中...</p>}
          {error && <p style={{ color: 'red' }}>错误:{error}</p>}

          {!loading && items.length === 0 && (
            <p style={{ color: '#888' }}>点击按钮加载文章</p>
          )}

          <ul>
            {items.slice(0, 10).map(post => (
              <li
                key={post.id}
                onClick={() => dispatch(selectPost(post.id))}
                style={{
                  padding: 8,
                  cursor: 'pointer',
                  background: selectedPostId === post.id ? '#e3f2fd' : 'transparent',
                  border: '1px solid #eee',
                  marginBottom: 4
                }}
              >
                <strong>{post.id}.</strong> {post.title}
              </li>
            ))}
          </ul>
        </div>

        {selectedPost && (
          <div style={{ flex: 1, borderLeft: '1px solid #ddd', paddingLeft: 20 }}>
            <h3>文章详情(Redux 选中状态)</h3>
            <p style={{ color: '#888', fontSize: 12 }}>
              当前选中 ID: {selectedPost.id}
            </p>
            <h4>{selectedPost.title}</h4>
            <p>{selectedPost.body}</p>
          </div>
        )}
      </div>
    </Provider>
  );
}

export default ReduxPostsApp;

---

四、运行效果说明

TodoApp:使用 useReducer + 清晰的状态和 action 定义。添加/删除/切换完成/筛选等功能全部正常工作,状态流转可预测且易于调试。

ReduxPostsApp:点击"加载用户 X 的文章",Redux 发起异步 thunk,loading 状态显示"加载中",完成后列表渲染文章;点击列表项,通过 Redux action 更新 selectedPostId,右侧详情区显示文章内容;Redux DevTools 可以完整看到每次状态变化的快照和 action 历史。

---

五、常见问题(FAQ)

#### Q1:useState、useReducer、Redux 分别用在什么场景?

  • <strong>useState</strong>:单组件内部的状态,不跨组件共享

  • <strong>useReducer</strong>:单组件内复杂状态,或用 Context 共享的全局状态

  • <strong>Redux</strong>:跨多个页面/路由的复杂全局状态,需要强大的调试工具(时间旅行)、中间件生态(saga/thunk)、或团队协作时

不要过度设计——如果你只有一个简单的全局状态(如当前主题),用 Context 就够了,不需要引入 Redux 的复杂度。

#### Q2:Redux Toolkit 和原生 Redux 的区别是什么?

Redux Toolkit 是 Redux 的官方推荐方式,它在原生 Redux 之上做了大量简化:

  • 内置 <code>createSlice</code>,用对象语法定义 reducers(比 switch-case 简洁得多)

  • 内置 <code>createAsyncThunk</code>,处理异步逻辑无需安装额外中间件

  • 默认启用 Redux DevTools,无需额外配置

  • 不可变性自动处理(Immer),可以直接修改 state 树

#### Q3:Redux 的状态更新是同步还是异步的?

默认是同步的。原生 Redux 的 dispatch 是同步的,只有通过中间件(如 redux-thunk 或 redux-saga)才能处理异步逻辑。Redux Toolkit 的 createAsyncThunk 内部也使用了 thunk 中间件来支持异步 dispatch。

---

六、延伸阅读

  • [React Hooks 完全指南:useState 与 useEffect 核心用法]()
  • [React Context API 状态管理深度理解]()
  • [React 自定义 Hooks 实战:逻辑复用与模块化]()
  • [React useCallback 与 useMemo 性能优化完全指南]()

---