# Redux完全指南

Redux是JavaScript应用的可预测状态容器,尤其适用于React应用。它帮助你编写行为一致、易于测试的应用,并提供出色的开发体验。本文将深入介绍Redux的核心概念、工作原理和最佳实践。

# 为什么需要Redux

在大型React应用中,组件状态管理变得复杂时,以下问题开始显现:

  1. 状态分散 - 状态分布在多个组件中,难以追踪和管理
  2. 状态共享困难 - 需要通过props多层传递或使用Context API
  3. 状态变化不可预测 - 多个组件可能以不同方式修改同一状态
  4. 调试困难 - 无法轻松回溯状态变化

Redux通过集中管理状态解决了这些问题。

# Redux核心概念

Redux架构基于三个基本原则:

  1. 单一数据源 - 整个应用的状态存储在单个store的对象树中
  2. 状态只读 - 唯一改变状态的方法是触发action
  3. 使用纯函数修改 - 使用reducer纯函数描述状态变化

# 1. Store

Store是Redux的核心,它保存整个应用的状态树:

import { createStore } from 'redux'
import rootReducer from './reducers'

const store = createStore(rootReducer)
1
2
3
4

# 2. Actions

Actions是描述发生了什么的简单对象,它们是改变状态的唯一方式:

// Action类型常量
export const ADD_TODO = 'ADD_TODO'
export const TOGGLE_TODO = 'TOGGLE_TODO'

// Action创建函数
export function addTodo(text) {
  return {
    type: ADD_TODO,
    payload: {
      id: Date.now(),
      text,
      completed: false
    }
  }
}

export function toggleTodo(id) {
  return {
    type: TOGGLE_TODO,
    payload: { id }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 3. Reducers

Reducers指定状态如何响应actions变化。它是一个纯函数,接收先前的状态和action,返回新状态:

import { ADD_TODO, TOGGLE_TODO } from './actions'

const initialState = {
  todos: []
}

function todoReducer(state = initialState, action) {
  switch (action.type) {
    case ADD_TODO:
      return {
        ...state,
        todos: [...state.todos, action.payload]
      }
    
    case TOGGLE_TODO:
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload.id
            ? { ...todo, completed: !todo.completed }
            : todo
        )
      }
    
    default:
      return state
  }
}

export default todoReducer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

对于复杂应用,可以使用combineReducers合并多个reducer:

import { combineReducers } from 'redux'
import todos from './todosReducer'
import auth from './authReducer'

const rootReducer = combineReducers({
  todos,
  auth
})

export default rootReducer
1
2
3
4
5
6
7
8
9
10

# Redux数据流

Redux遵循严格的单向数据流模式:

  1. 调用store.dispatch(action)
  2. Redux store调用reducer函数
  3. 根reducer将多个子reducer的结果合并成单个状态树
  4. Redux store保存reducer返回的完整状态树
  5. UI组件根据新状态更新

# 在React中使用Redux

# 设置React Redux

使用react-redux库将Redux与React集成:

npm install redux react-redux
# 或者
yarn add redux react-redux
1
2
3

# 连接组件

使用Providerconnect连接组件:

// 入口文件
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import rootReducer from './reducers'
import App from './components/App'

const store = createStore(rootReducer)

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

使用connect连接组件:

// TodoList.js
import React from 'react'
import { connect } from 'react-redux'
import { addTodo, toggleTodo } from '../actions'

function TodoList({ todos, addTodo, toggleTodo }) {
  const [text, setText] = React.useState('')
  
  const handleSubmit = e => {
    e.preventDefault()
    if (!text.trim()) return
    addTodo(text)
    setText('')
  }
  
  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input value={text} onChange={e => setText(e.target.value)} />
        <button type="submit">Add Todo</button>
      </form>
      
      <ul>
        {todos.map(todo => (
          <li
            key={todo.id}
            onClick={() => toggleTodo(todo.id)}
            style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
          >
            {todo.text}
          </li>
        ))}
      </ul>
    </div>
  )
}

const mapStateToProps = state => ({
  todos: state.todos
})

const mapDispatchToProps = {
  addTodo,
  toggleTodo
}

export default connect(mapStateToProps, mapDispatchToProps)(TodoList)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# 使用React Redux Hooks

React Redux 7.1引入了Hooks API,提供更简洁的方式连接组件:

import React, { useState } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { addTodo, toggleTodo } from '../actions'

function TodoList() {
  const [text, setText] = useState('')
  const todos = useSelector(state => state.todos)
  const dispatch = useDispatch()
  
  const handleSubmit = e => {
    e.preventDefault()
    if (!text.trim()) return
    dispatch(addTodo(text))
    setText('')
  }
  
  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input value={text} onChange={e => setText(e.target.value)} />
        <button type="submit">Add Todo</button>
      </form>
      
      <ul>
        {todos.map(todo => (
          <li
            key={todo.id}
            onClick={() => dispatch(toggleTodo(todo.id))}
            style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
          >
            {todo.text}
          </li>
        ))}
      </ul>
    </div>
  )
}

export default TodoList
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 处理异步操作

Redux本身只处理同步数据流。处理异步API调用等副作用,需要中间件。

# Redux Thunk

redux-thunk是最简单的异步中间件:

npm install redux-thunk
1

配置:

import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './reducers'

const store = createStore(
  rootReducer,
  applyMiddleware(thunk)
)
1
2
3
4
5
6
7
8

使用thunk创建异步action:

// 异步action创建器
export function fetchTodos() {
  return async function(dispatch) {
    dispatch({ type: 'FETCH_TODOS_REQUEST' })
    
    try {
      const response = await fetch('/api/todos')
      const data = await response.json()
      dispatch({
        type: 'FETCH_TODOS_SUCCESS',
        payload: data
      })
    } catch (error) {
      dispatch({
        type: 'FETCH_TODOS_FAILURE',
        error: error.message
      })
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# Redux Saga

对于复杂的异步流程,redux-saga提供更强大的方案:

npm install redux-saga
1

配置:

import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import rootReducer from './reducers'
import rootSaga from './sagas'

const sagaMiddleware = createSagaMiddleware()

const store = createStore(
  rootReducer,
  applyMiddleware(sagaMiddleware)
)

sagaMiddleware.run(rootSaga)
1
2
3
4
5
6
7
8
9
10
11
12
13

编写saga:

import { call, put, takeEvery } from 'redux-saga/effects'

// Worker Saga
function* fetchTodos() {
  try {
    yield put({ type: 'FETCH_TODOS_REQUEST' })
    const data = yield call(fetch, '/api/todos')
    const todos = yield data.json()
    yield put({ type: 'FETCH_TODOS_SUCCESS', payload: todos })
  } catch (error) {
    yield put({ type: 'FETCH_TODOS_FAILURE', error: error.message })
  }
}

// Watcher Saga
function* todoSaga() {
  yield takeEvery('FETCH_TODOS', fetchTodos)
}

export default todoSaga
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# Redux Toolkit

Redux Toolkit是Redux官方推荐的工具集,简化了Redux使用:

npm install @reduxjs/toolkit
1

使用Redux Toolkit创建slice:

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

// 创建异步thunk
export const fetchTodos = createAsyncThunk(
  'todos/fetchTodos',
  async () => {
    const response = await fetch('/api/todos')
    return response.json()
  }
)

const todosSlice = createSlice({
  name: 'todos',
  initialState: {
    items: [],
    status: 'idle',
    error: null
  },
  reducers: {
    addTodo: (state, action) => {
      state.items.push({
        id: Date.now(),
        text: action.payload,
        completed: false
      })
    },
    toggleTodo: (state, action) => {
      const todo = state.items.find(todo => todo.id === action.payload)
      if (todo) {
        todo.completed = !todo.completed
      }
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchTodos.pending, (state) => {
        state.status = 'loading'
      })
      .addCase(fetchTodos.fulfilled, (state, action) => {
        state.status = 'succeeded'
        state.items = action.payload
      })
      .addCase(fetchTodos.rejected, (state, action) => {
        state.status = 'failed'
        state.error = action.error.message
      })
  }
})

export const { addTodo, toggleTodo } = todosSlice.actions
export default todosSlice.reducer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

配置store:

import { configureStore } from '@reduxjs/toolkit'
import todosReducer from './todosSlice'
import authReducer from './authSlice'

const store = configureStore({
  reducer: {
    todos: todosReducer,
    auth: authReducer
  }
})

export default store
1
2
3
4
5
6
7
8
9
10
11
12

# 最佳实践

# 1. 状态规范化

对于嵌套或关联数据,规范化状态结构:

// 不好的例子 - 嵌套结构
const state = {
  authors: [
    {
      id: 1,
      name: '张三',
      books: [
        { id: 1, title: 'JavaScript高级程序设计' },
        { id: 2, title: 'React实战' }
      ]
    }
  ]
}

// 好的例子 - 规范化结构
const state = {
  authors: {
    byId: {
      1: { id: 1, name: '张三', bookIds: [1, 2] }
    },
    allIds: [1]
  },
  books: {
    byId: {
      1: { id: 1, title: 'JavaScript高级程序设计', authorId: 1 },
      2: { id: 2, title: 'React实战', authorId: 1 }
    },
    allIds: [1, 2]
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 2. 选择器函数

使用选择器(selectors)获取状态,提高重用性并封装状态结构:

// selectors.js
export const getTodos = state => state.todos.items
export const getTodoById = (state, id) => 
  state.todos.items.find(todo => todo.id === id)
export const getCompletedTodos = state => 
  state.todos.items.filter(todo => todo.completed)
1
2
3
4
5
6

使用选择器:

import { useSelector } from 'react-redux'
import { getTodos, getCompletedTodos } from '../selectors'

function TodoStats() {
  const todos = useSelector(getTodos)
  const completedTodos = useSelector(getCompletedTodos)
  
  return (
    <div>
      <p>总任务数: {todos.length}</p>
      <p>已完成: {completedTodos.length}</p>
      <p>完成率: {Math.round((completedTodos.length / todos.length) * 100)}%</p>
    </div>
  )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 3. 中间件优化

对于日志、崩溃报告等需求,使用中间件而非修改reducer:

import { createStore, applyMiddleware } from 'redux'
import logger from 'redux-logger'
import rootReducer from './reducers'

const crashReporter = store => next => action => {
  try {
    return next(action)
  } catch (err) {
    console.error('Caught an exception!', err)
    // 发送到错误跟踪服务
    Sentry.captureException(err, {
      extra: {
        action,
        state: store.getState()
      }
    })
    throw err
  }
}

const store = createStore(
  rootReducer,
  applyMiddleware(logger, crashReporter)
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 实际项目示例

以下是一个电子商务应用的Redux结构示例:

src/
├── store/
│   ├── index.js         # Store配置
│   ├── rootReducer.js   # 根Reducer
│   ├── auth/            # 认证模块
│   │   ├── slice.js     # Redux Toolkit slice
│   │   ├── selectors.js # 选择器
│   │   └── thunks.js    # 异步thunks
│   ├── products/        # 产品模块
│   │   ├── slice.js
│   │   ├── selectors.js
│   │   └── thunks.js
│   └── cart/            # 购物车模块
│       ├── slice.js
│       ├── selectors.js
│       └── thunks.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

在我在字节跳动的工作中,我们使用Redux管理大型数据分析平台的状态。通过遵循模块化结构和严格的数据流原则,即使在10+开发人员同时工作的情况下,我们也能保持应用的可维护性和可预测性。

# 迁移到Redux Toolkit

如果你有现有的Redux代码库,可以逐步迁移到Redux Toolkit:

// 旧代码
const ADD_TODO = 'ADD_TODO'
const TOGGLE_TODO = 'TOGGLE_TODO'

// Action创建器
export const addTodo = text => ({
  type: ADD_TODO,
  payload: { text }
})

// Reducer
const initialState = { todos: [] }
export default function todosReducer(state = initialState, action) {
  switch (action.type) {
    case ADD_TODO:
      return {
        ...state,
        todos: [...state.todos, { id: Date.now(), text: action.payload.text, completed: false }]
      }
    // ...其他case
    default:
      return state
  }
}

// 使用Redux Toolkit重写
import { createSlice } from '@reduxjs/toolkit'

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

export const { addTodo, toggleTodo } = todosSlice.actions
export default todosSlice.reducer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

# Redux vs. Context API

React的Context API提供了共享状态的原生方法,但与Redux相比有不同权衡:

特性 Redux Context API
状态管理 集中式 分散式
调试工具 强大 有限
中间件支持 丰富 需自行实现
性能优化 细粒度订阅 较广泛的重渲染
学习曲线 陡峭 平缓
适用场景 大型应用 小到中型应用

# 总结

Redux提供了一种可预测的状态管理方式,尤其适合复杂的React应用。其核心概念——单一数据源、只读状态和纯函数reducer——共同构成了一个强大而灵活的状态管理解决方案。

虽然学习曲线较陡,但Redux的长期收益显著。随着Redux Toolkit的发展,使用Redux变得更加简单和高效。

在选择状态管理解决方案时,请考虑项目规模、团队熟悉度和应用需求。对于大型、复杂的应用,Redux仍然是一个值得投资的解决方案。

# 学习资源

Last Updated: 2025-03-11T08:38:33.000Z