Redux 入门及实践指南
1. Redux 介绍
Redux 是 JavaScript 应用的状态容器,提供可预测的状态管理。它可以帮助你开发出行为稳定可预测的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。
为什么需要 Redux?
- 集中状态管理:所有的应用状态都存储在一个单一的 store 中
- 可预测性:状态的变化是通过纯函数(reducers)来进行的,确保状态变化的可预测性
- 性能优化:通过浅比较来避免不必要的重渲染
- 开发工具支持:强大的开发工具帮助跟踪状态变化
- 状态持久化:便于实现数据持久化和状态恢复
2. Redux 核心概念
- Store:存储应用的状态
- Action:描述发生了什么的普通对象
- Reducer:根据 action 更新状态的纯函数
- 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 异步操作的优势
简化的异步处理:
- 使用
createAsyncThunk
自动处理异步操作的三种状态(pending/fulfilled/rejected) - 不需要手动创建 action types 和 action creators
- 自动处理加载状态和错误状态
- 使用
更简洁的代码:
- 不需要手动设置 thunk middleware
- 可以在
extraReducers
中直接使用 builder 模式处理异步状态 - 可以直接修改状态(Immer 会处理不可变更新)
更好的类型支持:
- TypeScript 支持更好
- 自动推断 action 和 state 类型
内置错误处理:
- 自动捕获和处理错误
- 错误信息会被自动传递到 rejected 状态
6. Redux Toolkit 的优势
- 简化配置:内置了很多有用的默认配置
- 减少样板代码:使用 createSlice 自动生成 action creators 和 action types
- 内置不可变更新逻辑:可以直接修改状态,Redux Toolkit 内部使用 Immer 处理不可变更新
- 默认包含有用的中间件:默认包含 redux-thunk,并启用 Redux DevTools
7. 最佳实践建议
- 使用 Redux Toolkit:它是官方推荐的方式,可以大大简化开发流程
- 合理组织 State 结构:按照领域模型或功能模块组织状态
- 使用选择器(Selectors):通过选择器访问状态,提高代码复用性和维护性
- 异步操作使用 createAsyncThunk:处理异步操作时,推荐使用 Redux Toolkit 的 createAsyncThunk
- 遵循 Redux 的三大原则:
- 单一数据源
- State 是只读的
- 使用纯函数进行修改
8. 调试工具
推荐使用 Redux DevTools 进行调试:
- 安装浏览器扩展(Chrome/Firefox)
- 在开发时可以查看:
- Action 历史
- State 变化
- 时间旅行调试
- Action 回放
9. 总结
Redux 的发展经历了从原生 Redux 到 React-Redux,再到 Redux Toolkit 的过程。每一次演进都是为了简化开发流程,减少样板代码。现在推荐直接使用 Redux Toolkit 开发新项目,它提供了最佳的开发体验和工具支持。
记住,虽然 Redux 很强大,但不是所有项目都需要使用它。如果你的应用状态管理较为简单,使用 React 的 Context API 和 useReducer 可能就足够了。在选择状态管理方案时,要根据项目的实际需求来决定。