Skip to content

Redux 入门及实践指南

1. Redux 介绍

Redux 是 JavaScript 应用的状态容器,提供可预测的状态管理。它可以帮助你开发出行为稳定可预测的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。

为什么需要 Redux?

  • 集中状态管理:所有的应用状态都存储在一个单一的 store 中
  • 可预测性:状态的变化是通过纯函数(reducers)来进行的,确保状态变化的可预测性
  • 性能优化:通过浅比较来避免不必要的重渲染
  • 开发工具支持:强大的开发工具帮助跟踪状态变化
  • 状态持久化:便于实现数据持久化和状态恢复

2. Redux 核心概念

  1. Store:存储应用的状态
  2. Action:描述发生了什么的普通对象
  3. Reducer:根据 action 更新状态的纯函数
  4. Dispatch:发送 action 的方法

3. 原生 Redux 使用教程

让我们通过一个简单的计数器示例来学习 Redux:

3.1 基础示例:计数器

javascript
// counter.js
const { createStore } = require('redux')

// 1. 定义初始状态
const initialState = {
  count: 0
}

// 2. 创建 reducer
const counterReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return {
        ...state,
        count: state.count + 1
      }
    case 'DECREMENT':
      return {
        ...state,
        count: state.count - 1
      }
    default:
      return state
  }
}

// 3. 创建 store
const store = createStore(counterReducer)

// 4. 订阅状态变化
store.subscribe(() => {
  console.log('当前状态:', store.getState())
})

// 5. 发送 action
store.dispatch({ type: 'INCREMENT' })  // 计数器 +1
store.dispatch({ type: 'INCREMENT' })  // 计数器 +1
store.dispatch({ type: 'DECREMENT' })  // 计数器 -1

4. React-Redux 的使用

React-Redux 是 Redux 的官方 React 绑定库,提供了更便捷的 API。

4.1 使用 React-Redux 的计数器示例

jsx
// App.jsx
import React from 'react'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import Counter from './Counter'

const initialState = {
  count: 0
}

const counterReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return {
        ...state,
        count: state.count + 1
      }
    case 'DECREMENT':
      return {
        ...state,
        count: state.count - 1
      }
    default:
      return state
  }
}

const store = createStore(counterReducer)

function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  )
}

export default App
jsx
// Counter.jsx
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'

function Counter() {
  const count = useSelector(state => state.count)
  const dispatch = useDispatch()

  return (
    <div>
      <h1>计数器: {count}</h1>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>增加</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>减少</button>
    </div>
  )
}

export default Counter

4.2 使用 React-Redux 的异步示例

jsx
// store.js
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'

const initialState = {
  todos: [],
  loading: false,
  error: null
}

// Action Types 和 Action Creators
export const FETCH_TODOS_START = 'FETCH_TODOS_START'
export const FETCH_TODOS_SUCCESS = 'FETCH_TODOS_SUCCESS'
export const FETCH_TODOS_FAILURE = 'FETCH_TODOS_FAILURE'

export const fetchTodosStart = () => ({ type: FETCH_TODOS_START })
export const fetchTodosSuccess = (todos) => ({ type: FETCH_TODOS_SUCCESS, payload: todos })
export const fetchTodosFailure = (error) => ({ type: FETCH_TODOS_FAILURE, payload: error })

// 异步 Action Creator
export const fetchTodos = () => async (dispatch) => {
  dispatch(fetchTodosStart())
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/todos')
    const todos = await response.json()
    dispatch(fetchTodosSuccess(todos))
  } catch (error) {
    dispatch(fetchTodosFailure(error.message))
  }
}

// Reducer
const todosReducer = (state = initialState, action) => {
  switch (action.type) {
    case FETCH_TODOS_START:
      return { ...state, loading: true }
    case FETCH_TODOS_SUCCESS:
      return { ...state, loading: false, todos: action.payload }
    case FETCH_TODOS_FAILURE:
      return { ...state, loading: false, error: action.payload }
    default:
      return state
  }
}

export const store = createStore(todosReducer, applyMiddleware(thunk))
jsx
// TodoList.jsx
import React, { useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { fetchTodos } from './store'

function TodoList() {
  const dispatch = useDispatch()
  const { todos, loading, error } = useSelector(state => state)

  useEffect(() => {
    dispatch(fetchTodos())
  }, [dispatch])

  if (loading) return <div>加载中...</div>
  if (error) return <div>错误:{error}</div>

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          {todo.title}
          {todo.completed ? ' ✓' : ''}
        </li>
      ))}
    </ul>
  )
}

export default TodoList

5. Redux Toolkit 的使用

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

5.1 使用 Redux Toolkit 的计数器示例

jsx
// counterSlice.js
import { createSlice } from '@reduxjs/toolkit'

const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    count: 0
  },
  reducers: {
    increment: (state) => {
      state.count += 1
    },
    decrement: (state) => {
      state.count -= 1
    }
  }
})

export const { increment, decrement } = counterSlice.actions
export default counterSlice.reducer
jsx
// store.js
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './counterSlice'

export const store = configureStore({
  reducer: {
    counter: counterReducer
  }
})
jsx
// App.jsx
import React from 'react'
import { Provider } from 'react-redux'
import { store } from './store'
import Counter from './Counter'

function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  )
}

export default App
jsx
// Counter.jsx
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { increment, decrement } from './counterSlice'

function Counter() {
  const count = useSelector(state => state.counter.count)
  const dispatch = useDispatch()

  return (
    <div>
      <h1>计数器: {count}</h1>
      <button onClick={() => dispatch(increment())}>增加</button>
      <button onClick={() => dispatch(decrement())}>减少</button>
    </div>
  )
}

export default Counter

5.2 使用 Redux Toolkit 的异步示例

jsx
// todosSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'

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

const todosSlice = createSlice({
  name: 'todos',
  initialState: {
    todos: [],
    loading: false,
    error: null
  },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchTodos.pending, (state) => {
        state.loading = true
      })
      .addCase(fetchTodos.fulfilled, (state, action) => {
        state.loading = false
        state.todos = action.payload
      })
      .addCase(fetchTodos.rejected, (state, action) => {
        state.loading = false
        state.error = action.error.message
      })
  }
})

export default todosSlice.reducer
jsx
// store.js
import { configureStore } from '@reduxjs/toolkit'
import todosReducer from './todosSlice'

export const store = configureStore({
  reducer: {
    todos: todosReducer
  }
})
jsx
// TodoList.jsx
import React, { useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { fetchTodos } from './todosSlice'

function TodoList() {
  const dispatch = useDispatch()
  const { todos, loading, error } = useSelector(state => state.todos)

  useEffect(() => {
    dispatch(fetchTodos())
  }, [dispatch])

  if (loading) return <div>加载中...</div>
  if (error) return <div>错误:{error}</div>

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          {todo.title}
          {todo.completed ? ' ✓' : ''}
        </li>
      ))}
    </ul>
  )
}

export default TodoList

5.3 Redux Toolkit 异步操作的优势

  1. 简化的异步处理

    • 使用 createAsyncThunk 自动处理异步操作的三种状态(pending/fulfilled/rejected)
    • 不需要手动创建 action types 和 action creators
    • 自动处理加载状态和错误状态
  2. 更简洁的代码

    • 不需要手动设置 thunk middleware
    • 可以在 extraReducers 中直接使用 builder 模式处理异步状态
    • 可以直接修改状态(Immer 会处理不可变更新)
  3. 更好的类型支持

    • TypeScript 支持更好
    • 自动推断 action 和 state 类型
  4. 内置错误处理

    • 自动捕获和处理错误
    • 错误信息会被自动传递到 rejected 状态

6. Redux Toolkit 的优势

  1. 简化配置:内置了很多有用的默认配置
  2. 减少样板代码:使用 createSlice 自动生成 action creators 和 action types
  3. 内置不可变更新逻辑:可以直接修改状态,Redux Toolkit 内部使用 Immer 处理不可变更新
  4. 默认包含有用的中间件:默认包含 redux-thunk,并启用 Redux DevTools

7. 最佳实践建议

  1. 使用 Redux Toolkit:它是官方推荐的方式,可以大大简化开发流程
  2. 合理组织 State 结构:按照领域模型或功能模块组织状态
  3. 使用选择器(Selectors):通过选择器访问状态,提高代码复用性和维护性
  4. 异步操作使用 createAsyncThunk:处理异步操作时,推荐使用 Redux Toolkit 的 createAsyncThunk
  5. 遵循 Redux 的三大原则
    • 单一数据源
    • State 是只读的
    • 使用纯函数进行修改

8. 调试工具

推荐使用 Redux DevTools 进行调试:

  1. 安装浏览器扩展(Chrome/Firefox)
  2. 在开发时可以查看:
    • Action 历史
    • State 变化
    • 时间旅行调试
    • Action 回放

9. 总结

Redux 的发展经历了从原生 Redux 到 React-Redux,再到 Redux Toolkit 的过程。每一次演进都是为了简化开发流程,减少样板代码。现在推荐直接使用 Redux Toolkit 开发新项目,它提供了最佳的开发体验和工具支持。

记住,虽然 Redux 很强大,但不是所有项目都需要使用它。如果你的应用状态管理较为简单,使用 React 的 Context API 和 useReducer 可能就足够了。在选择状态管理方案时,要根据项目的实际需求来决定。

本站所有内容均为原创,转载请注明出处