Redux 与 React-Redux 深度解析:从原理到最佳实践

Redux 与 React-Redux 深度解析:从原理到最佳实践

前言

在 React 应用开发中,状态管理始终是一个核心议题。当应用规模逐渐扩大,组件间的状态共享变得复杂,Props 透传问题开始显现,这时候我们就需要一个专业的状态管理方案。Redux 作为 React 生态中最经典的状态管理库,虽然学习曲线较陡,但一旦掌握,就能让你的应用状态变得可预测、可维护。

本文将带你深入理解 Redux 的核心设计思想,并详细介绍 React-Redux 的集成方式,最后还会聊聊现代 Redux 的最佳实践。

一、为什么需要 Redux?

先来看一个典型的 Props 透传问题:

jsx 复制代码
// 组件树结构
<App>
  <Header>
    <UserAvatar>
      <AvatarImage /> // 这里需要用户的头像URL
    </UserAvatar>
  </Header>
  <Sidebar />
  <Content>
    <PostList>
      <PostItem /> // 这里也需要用户信息来判断是否可编辑
    </PostList>
  </Content>
</App>

用户信息可能需要在 AvatarImagePostItem 中共享,但按照 React 单向数据流,我们不得不把用户信息从 App 一层层传递下去------这就是 Props Drilling 问题。

Redux 通过提供一个全局的、唯一的状态树,让任何组件都能直接访问需要的状态,彻底解决了这个问题。

二、Redux 三大核心原则

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

整个应用的状态存储在一个 JavaScript 对象树中,并且这个对象树只存在于唯一一个 Store 中。这让调试、持久化、时间旅行调试变得异常简单。

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

唯一改变状态的方式是触发一个 Action(一个描述"发生了什么"的普通对象)。这确保了视图和网络请求都无法直接修改状态,所有变化都被集中处理。

3. Changes are Made with Pure Functions(使用纯函数执行修改)

为了描述 Action 如何改变状态树,你需要编写 Reducer。Reducer 是纯函数,它接收先前的状态和 Action,并返回新的状态。

javascript 复制代码
// 纯函数示例
function counterReducer(state = 0, action) {
  switch(action.type) {
    case 'INCREMENT':
      return state + 1;  // 返回新对象,不修改原state
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
}

三、Redux 核心概念详解

Action

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

javascript 复制代码
// Action 定义
const ADD_TODO = 'ADD_TODO';

const addTodo = (text) => ({
  type: ADD_TODO,
  payload: {
    id: Date.now(),
    text,
    completed: false
  }
});

// 实际使用
dispatch(addTodo('学习 Redux'));

Reducer

Reducer 负责响应 Action 并更新状态。记住:永远不要在 reducer 中做这些事

  • 修改传入的参数
  • 执行有副作用操作(API 请求、路由跳转)
  • 调用非纯函数(如 Date.now()、Math.random())
javascript 复制代码
// 错误的 reducer - 直接修改原数组
function todosReducer(state = [], action) {
  switch(action.type) {
    case 'ADD_TODO':
      state.push(action.payload); // ❌ 不要这样做!
      return state;
    default:
      return state;
  }
}

// 正确的做法 - 返回新数组
function todosReducer(state = [], action) {
  switch(action.type) {
    case 'ADD_TODO':
      return [...state, action.payload]; // ✅ 返回新对象
    default:
      return state;
  }
}

Store

Store 就是把 Action 和 Reducer 联系起来的对象。Store 的职责:

  • 维持应用的 state
  • 提供 getState() 方法获取 state
  • 提供 dispatch(action) 方法更新 state
  • 提供 subscribe(listener) 注册监听器
javascript 复制代码
import { createStore } from 'redux';

const store = createStore(todosReducer);

// 获取状态
console.log(store.getState());

// 订阅变化
store.subscribe(() => {
  console.log('State updated:', store.getState());
});

// 派发 action
store.dispatch(addTodo('写一篇 Redux 博客'));

完整 Redux 简单使用示例(纯 Redux,不含 React)

下面是一个完整的计数器示例,展示 Redux 的完整工作流程:

javascript 复制代码
// 1. 定义 Action Types
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

// 2. 编写 Action Creators
const increment = () => ({ type: INCREMENT ,data:1});
const decrement = () => ({ type: DECREMENT ,data:1});

// 3. 编写 Reducer
const counterReducer = (state = 0, action) => {
  switch (action.type) {
    case INCREMENT:
      return state + action.data;
    case DECREMENT:
      return state - action.data;
    default:
      return state;
  }
};

// 4. 创建 Store
const { createStore } = require('redux');
const store = createStore(counterReducer);

// 5. 订阅状态变化(可选)
store.subscribe(() => {
  console.log('当前计数:', store.getState());
});

// 6. 派发 Action
store.dispatch(increment()); // 输出:当前计数: 1
store.dispatch(increment()); // 输出:当前计数: 2
store.dispatch(decrement()); // 输出:当前计数: 1

// 最终状态
console.log(store.getState()); // 1

明白了,您希望我重点描述 React-Redux 的架构设计,而不是历史演变过程。下面我重新撰写第四部分,聚焦于 React-Redux 的架构组成(Provider、connect、HOC、Context、Hooks)以及它们如何协同工作。


四、React-Redux:连接 React 和 Redux 的架构解析

为什么需要 React-Redux 架构?

React 和 Redux 是两个独立的库:

  • React:负责 UI 渲染,通过组件树管理视图。
  • Redux:负责全局状态管理,提供 store 和纯函数的更新逻辑。

要让它们协同工作,必须解决两个核心问题:

  1. 状态注入:如何让任意层级的 React 组件都能访问到 Redux store。
  2. 响应式更新:当 store 状态变化时,如何自动重新渲染依赖该状态的组件。

React-Redux 通过一套清晰的架构解决了这些问题,其核心包括:Providerconnect (高阶组件)、Context 、以及现代的 Hooks API

React-Redux 整体架构图

ini 复制代码
┌─────────────────────────────────────────────────────┐
│                     React App                        │
│  ┌─────────────────────────────────────────────┐    │
│  │              <Provider store={store}>        │    │
│  │  ┌─────────────────────────────────────────┐ │    │
│  │  │         React Component Tree             │ │    │
│  │  │  ┌─────────────────────────────────┐   │ │    │
│  │  │  │   Container Component (connect)  │   │ │    │
│  │  │  │         ┌─────────────┐          │   │ │    │
│  │  │  │         │  UI Component│          │   │ │    │
│  │  │  │         └─────────────┘          │   │ │    │
│  │  │  └─────────────────────────────────┘   │ │    │
│  │  │  or Hooks: useSelector + useDispatch   │ │    │
│  │  └─────────────────────────────────────────┘ │    │
│  └─────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────┘
         │                              │
         │ subscribe                    │ dispatch
         ▼                              ▼
┌─────────────────────────────────────────────────────┐
│                    Redux Store                        │
│  ┌──────────┐   ┌──────────┐   ┌──────────┐         │
│  │ reducer1 │   │ reducer2 │   │ reducer3 │ ...     │
│  └──────────┘   └──────────┘   └──────────┘         │
│                    State                              │
└─────────────────────────────────────────────────────┘

架构核心部件详解

1. Provider 组件 -- 状态注入器

Provider 是 React-Redux 提供的顶层组件,它利用 React 的 Context API 将 Redux store 注入到整个组件树中,使得任何后代组件都能通过 connect 或 Hooks 访问到 store。

jsx 复制代码
import { Provider } from 'react-redux';
import { store } from './store';
import App from './App';

<Provider store={store}>
  <App />
</Provider>

Provider 的内部原理

  • Provider 内部维护一个 React Context,并将 store 对象作为 context 的值。
  • 同时,Provider 会订阅 store 的变化,当 store 更新时,强制重新渲染所有使用了该 Context 的组件(经过优化,只有订阅了状态变化的组件才会重绘)。
2. connect 高阶组件 -- 容器组件的生成器

connect 是 React-Redux 中最经典的核心 API。它是一个高阶组件(HOC),接收配置参数,返回一个新的函数,这个函数再接收 UI 组件,最终生成一个容器组件

jsx 复制代码
connect(mapStateToProps, mapDispatchToProps)(UIComponent)

connect 的职责

  • 从 Context 中获取 store。
  • 订阅 store 的变化。
  • 执行 mapStateToProps 提取需要的状态片段,作为 props 传递给 UI 组件。
  • mapDispatchToProps 返回的 action 包装成 dispatch 调用,也作为 props 传递给 UI 组件。
  • 当 store 变化时,重新计算 mapStateToProps,若结果与之前不同,则触发 UI 组件重新渲染。

mapStateToProps 和 mapDispatchToProps 的本质

javascript 复制代码
// mapStateToProps:定义如何将 store 中的状态映射到 UI 组件的 props
const mapStateToProps = (state, ownProps) => ({
  count: state.counter,
  userName: state.user.name
});

// mapDispatchToProps:定义如何将 dispatch 动作映射到 UI 组件的 props
const mapDispatchToProps = (dispatch) => ({
  addTodo: (text) => dispatch({ type: 'ADD_TODO', payload: text }),
  // 也可以使用 bindActionCreators
});
3. UI 组件与容器组件的分离架构

React-Redux 强烈推荐将组件拆分为:

  • UI 组件(Presentational Components) :只负责渲染,不直接与 Redux 交互。所有数据通过 props 传入,无副作用,易于测试和复用。
  • 容器组件(Container Components) :负责与 Redux 通信,通过 connect 生成,将状态和行为传递给 UI 组件。
jsx 复制代码
// UI 组件:只关心如何显示
const TodoListUI = ({ todos, onToggle, onDelete }) => (
  <ul>
    {todos.map(todo => (
      <li key={todo.id}>
        <span onClick={() => onToggle(todo.id)}>{todo.text}</span>
        <button onClick={() => onDelete(todo.id)}>删除</button>
      </li>
    ))}
  </ul>
);

// 容器组件:连接 Redux
import { connect } from 'react-redux';
import { toggleTodo, deleteTodo } from './actions';

const mapStateToProps = (state) => ({ todos: state.todos });
const mapDispatchToProps = { toggleTodo, deleteTodo };

const TodoListContainer = connect(mapStateToProps, mapDispatchToProps)(TodoListUI);

这种架构带来的好处:

  • 关注点分离:UI 逻辑和状态管理逻辑解耦。
  • 可测试性:UI 组件可以独立测试(只需传入 props),容器组件的测试可以通过模拟 store 进行。
  • 复用性:同一个 UI 组件可以配合不同的数据源使用。
4. Hooks API -- 现代的轻量级替代

从 React-Redux v7.1 开始,Hooks API 提供了更简洁的写法,但底层依然依赖 Provider 和 Context。Hooks 让每个组件内部自己"连接" Redux,无需显式拆分容器和 UI 组件(但思想依然可沿用)。

jsx 复制代码
import { useSelector, useDispatch } from 'react-redux';

function TodoList() {
  const todos = useSelector(state => state.todos);
  const dispatch = useDispatch();

  const handleToggle = (id) => dispatch(toggleTodo(id));
  const handleDelete = (id) => dispatch(deleteTodo(id));

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          <span onClick={() => handleToggle(todo.id)}>{todo.text}</span>
          <button onClick={() => handleDelete(todo.id)}>删除</button>
        </li>
      ))}
    </ul>
  );
}

useSelector 的工作原理

  • 内部使用 Context 获取 store。
  • 通过 store.subscribe 监听变化,并在回调中比较当前选取的状态和之前的状态(默认使用严格相等 ===)。
  • 如果选取的状态发生变化,则强制组件重新渲染。

useDispatch 则直接返回 store 的 dispatch 函数,非常简单。

架构总结:数据流向

scss 复制代码
用户交互 → 组件调用 dispatch(action) 
         → Redux store 执行 reducer 更新状态
         → store 通知所有订阅者
         → React-Redux 内部(connect 或 useSelector)比较前后状态
         → 若相关状态变化,触发组件重新渲染
         → UI 更新

为什么这种架构是必要的?

如果没有 React-Redux,你需要手动:

  • 在根组件通过 Context 传递 store。
  • 在每个需要 store 的组件中订阅 store.subscribe 并处理卸载。
  • 手动优化性能,防止不必要的渲染。

五、多个 Reducer 的管理:combineReducers

当应用变得复杂,将所有状态放在一个 reducer 里会导致函数臃肿难维护。Redux 提供了 combineReducers 工具函数,可以将多个 reducer 合并成一个根 reducer。

使用 combineReducers 管理多个 Reducer

javascript 复制代码
// reducers/todosReducer.js
const todosReducer = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, { id: Date.now(), text: action.payload, completed: false }];
    case 'TOGGLE_TODO':
      return state.map(todo =>
        todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo
      );
    default:
      return state;
  }
};

// reducers/userReducer.js
const userReducer = (state = { name: '', loggedIn: false }, action) => {
  switch (action.type) {
    case 'LOGIN':
      return { ...state, name: action.payload.name, loggedIn: true };
    case 'LOGOUT':
      return { ...state, name: '', loggedIn: false };
    default:
      return state;
  }
};

// reducers/index.js - 合并 reducer
import { combineReducers } from 'redux';
import todosReducer from './todosReducer';
import userReducer from './userReducer';

const rootReducer = combineReducers({
  todos: todosReducer,
  user: userReducer
});

// 创建 store
import { createStore } from 'redux';
const store = createStore(rootReducer);

// 此时 store 的状态结构为:
// {
//   todos: [],
//   user: { name: '', loggedIn: false }
// }

// 派发 action 示例
store.dispatch({ type: 'ADD_TODO', payload: '学习 combineReducers' });
store.dispatch({ type: 'LOGIN', payload: { name: '张三' } });

console.log(store.getState());
/*
{
  todos: [ { id: 123, text: '学习 combineReducers', completed: false } ],
  user: { name: '张三', loggedIn: true }
}
*/

重要特性:

  • combineReducers 会自动根据 state 的 key 调用对应的 reducer。
  • 每个子 reducer 只会收到自己对应的 state 片段,返回的更新也会合并到新的总 state 中。
  • 如果某个 reducer 返回的 state 未改变(严格相等),那么总 state 的对应片段也不会变,这有助于性能优化。

在 React-Redux 中使用多个 Reducer

使用 combineReducers 后,在组件中通过 useSelector 获取状态时需要指定完整路径:

jsx 复制代码
// 获取 todos 列表
const todos = useSelector(state => state.todos);

// 获取用户信息
const userName = useSelector(state => state.user.name);

Redux Toolkit 中的多 Reducer 管理

Redux Toolkit 的 configureStore 自动帮你调用 combineReducers

javascript 复制代码
import { configureStore } from '@reduxjs/toolkit';
import todosReducer from './todosSlice';
import userReducer from './userSlice';

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

configureStore 还内置了 redux-thunk、开发工具支持等,推荐在现代项目中使用。

六、现代 Redux:Redux Toolkit (RTK)

传统 Redux 的痛点:

  • 配置 store 太繁琐
  • 需要安装大量额外包
  • 编写 reducer 时容易写错不可变更新逻辑

Redux Toolkit 是官方推荐的工具集,大幅简化了 Redux 的开发体验。

创建 Slice

javascript 复制代码
// features/counter/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0,
    status: 'idle'
  },
  reducers: {
    increment: (state) => {
      // RTK 内部使用 Immer,可以直接写可变代码!
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    }
  }
});

// 自动生成 action creators
export const { increment, decrement, incrementByAmount } = counterSlice.actions;

// 自动生成 reducer
export default counterSlice.reducer;

配置 Store

javascript 复制代码
// app/store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
import todosReducer from '../features/todos/todosSlice';

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

异步处理:createAsyncThunk

javascript 复制代码
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { fetchUserAPI } from './api';

// 创建异步 thunk
export const fetchUser = createAsyncThunk(
  'user/fetchUser',
  async (userId, thunkAPI) => {
    const response = await fetchUserAPI(userId);
    return response.data;
  }
);

const userSlice = createSlice({
  name: 'user',
  initialState: {
    data: null,
    status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
    error: null
  },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchUser.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(fetchUser.fulfilled, (state, action) => {
        state.status = 'succeeded';
        state.data = action.payload;
      })
      .addCase(fetchUser.rejected, (state, action) => {
        state.status = 'failed';
        state.error = action.error.message;
      });
  }
});

export default userSlice.reducer;

七、性能优化最佳实践

1. 精细化 useSelector

javascript 复制代码
// ❌ 不好:每次返回新对象,会导致组件总是重新渲染
const { name, age } = useSelector(state => ({
  name: state.user.name,
  age: state.user.age
}));

// ✅ 好:分别获取,或者使用 shallowEqual
const name = useSelector(state => state.user.name);
const age = useSelector(state => state.user.age);

// 或者使用 shallowEqual 进行浅比较
import { shallowEqual, useSelector } from 'react-redux';
const user = useSelector(state => ({
  name: state.user.name,
  age: state.user.age
}), shallowEqual);

2. 使用 Reselect 创建记忆化的 Selector

javascript 复制代码
import { createSelector } from '@reduxjs/toolkit';

const selectTodos = state => state.todos;
const selectFilter = state => state.visibilityFilter;

// 只有当 todos 或 filter 变化时才重新计算
export const selectFilteredTodos = createSelector(
  [selectTodos, selectFilter],
  (todos, filter) => {
    switch(filter) {
      case 'active':
        return todos.filter(t => !t.completed);
      case 'completed':
        return todos.filter(t => t.completed);
      default:
        return todos;
    }
  }
);

3. 避免在组件中直接 dispatch 复杂逻辑

javascript 复制代码
// ❌ 不好:业务逻辑写在组件里
function TodoApp() {
  const dispatch = useDispatch();
  
  const handleAddTodo = (text) => {
    dispatch({ type: 'TODO/START_LOADING' });
    api.addTodo(text)
      .then(todo => {
        dispatch({ type: 'TODO/ADD_SUCCESS', payload: todo });
      })
      .catch(err => {
        dispatch({ type: 'TODO/ADD_FAILURE', error: err });
      });
  };
}

// ✅ 好:使用 createAsyncThunk 或封装的 action creator
function TodoApp() {
  const dispatch = useDispatch();
  const handleAddTodo = (text) => dispatch(addTodoAsync(text));
}

八、Redux vs 其他状态管理方案

方案 优点 缺点 适用场景
Redux 可预测性强、生态丰富、时间旅行调试 模板代码多、学习曲线陡 大型复杂应用
Zustand 极其简洁、无模板代码、轻量 生态不如 Redux 中小型应用
Context + useReducer 原生、零依赖 性能优化困难、无中间件 简单状态共享
MobX 响应式、可变写法 隐式依赖、调试复杂 偏好 OOP 的项目

九、实战:一个完整的 Todo 应用(使用 RTK)

javascript 复制代码
// store.js
import { configureStore, createSlice } from '@reduxjs/toolkit';

const todosSlice = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    addTodo: (state, action) => {
      state.push({ id: Date.now(), text: action.payload, completed: false });
    },
    toggleTodo: (state, action) => {
      const todo = state.find(t => t.id === action.payload);
      if (todo) todo.completed = !todo.completed;
    },
    deleteTodo: (state, action) => {
      return state.filter(t => t.id !== action.payload);
    }
  }
});

export const { addTodo, toggleTodo, deleteTodo } = todosSlice.actions;
export const store = configureStore({ reducer: { todos: todosSlice.reducer } });

// TodoApp.jsx
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { addTodo, toggleTodo, deleteTodo } from './store';

function TodoApp() {
  const [text, setText] = useState('');
  const todos = useSelector(state => state.todos);
  const dispatch = useDispatch();

  const handleSubmit = (e) => {
    e.preventDefault();
    if (text.trim()) {
      dispatch(addTodo(text));
      setText('');
    }
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input value={text} onChange={e => setText(e.target.value)} />
        <button type="submit">添加</button>
      </form>
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            <span
              style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
              onClick={() => dispatch(toggleTodo(todo.id))}
            >
              {todo.text}
            </span>
            <button onClick={() => dispatch(deleteTodo(todo.id))}>删除</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default TodoApp;

结语

Redux 经历了多年的发展,从最初繁琐的手写 reducer,到如今 Redux Toolkit 带来的丝滑体验,它依然是 React 生态中最成熟、最可靠的状态管理方案。理解 Redux 的核心思想------单向数据流、不可变更新、纯函数 reducer------不仅能让你用好 Redux,也会让你对前端状态管理有更深刻的认知。

对于新项目,强烈推荐直接使用 Redux Toolkit + React-Redux Hooks API。这套组合能让你在享受 Redux 强大能力的同时,保持代码的简洁和可维护性。

希望这篇文章能帮助你更好地理解和应用 Redux。如果你有任何问题或经验分享,欢迎在评论区留言讨论!

相关推荐
微扬嘴角1 小时前
react篇4--setState、LazyLoad和Hooks
前端·javascript·react.js
光影少年3 小时前
React 项目常见优化方案
前端·react.js·前端框架
光影少年4 小时前
组件复用:HOC、Render Props、自定义Hook 对比
前端·react.js·掘金·金石计划
放下华子我只抽RuiKe55 小时前
FastAPI 全栈后端(二):路由与数据模型
前端·人工智能·react.js·前端框架·html·fastapi
YFF菲菲兔8 小时前
prepareFreshStack 源码解析
react.js
Aolith8 小时前
从 Pinia 到 Zustand:我在 React 里复刻了一套用户状态管理
前端·react.js·typescript
右耳朵猫AI8 小时前
React周刊2026W22 | React 13周年、React Router 7.16.0、Spoiled 0.5
前端·react.js·前端框架
右耳朵猫AI9 小时前
前端周刊2026W22 | React 13周年、TanStack Router、Deno 2.8、Node.js 26、npm 分阶段发布
前端·react.js·node.js
产品研究员1 天前
AI生成可用的React交互代码实测:Lovable vs Stitch vs Paico
前端·react.js·aigc