引言:Redux 在现代前端开发中的重要性
在当今复杂的前端应用开发中,状态管理已成为决定项目成败的关键因素。Redux 作为一个成熟、可预测的状态容器,凭借其清晰的架构设计和强大的生态系统,已经成为 React 生态中最受欢迎的状态管理解决方案之一。本指南将从零基础开始,逐步深入到高级应用,帮助开发者全面掌握 Redux 的使用技巧和最佳实践。
Redux 的核心优势在于其单向数据流和可预测的状态变化。通过将应用状态集中存储在一个单一的 store 中,并通过纯函数来管理状态的变更,Redux 使得应用的状态变化变得可追踪、可调试。这种设计不仅提高了代码的可维护性,也为大型团队协作提供了坚实的基础。
第一部分:Redux 基础概念与核心原理
1.1 Redux 的三大基本原则
Redux 的设计遵循三个核心原则,这些原则是理解 Redux 工作原理的基础:
单一数据源(Single Source of Truth)
整个应用的状态应该存储在一个单一的 JavaScript 对象中,这个对象被称为 store。这种设计使得状态管理变得简单,因为任何时候你只需要查看一个地方就能了解整个应用的状态。
// 示例:完整的应用状态结构
const initialState = {
user: {
id: 1,
name: “张三”,
email: “zhangsan@example.com”,
isAuthenticated: true
},
posts: {
items: [],
loading: false,
error: null
},
ui: {
sidebarOpen: true,
theme: “light”
}
};
状态是只读的(State is Read-Only)
唯一改变状态的方法是触发 action。这确保了所有的状态变更都是显式的,不会发生隐式的状态修改。
// 错误的做法:直接修改状态
// state.user.name = “李四”; // ❌ 这样做是错误的
// 正确的做法:通过 action 修改状态
const action = {
type: ‘USER_UPDATE_NAME’,
payload: { name: ‘李四’ }
};
store.dispatch(action);
使用纯函数执行修改(Changes are made with Pure Functions)
描述状态变化的 reducers 必须是纯函数,给定相同的输入总是返回相同的输出,且不产生副作用。
// 纯函数示例:user reducer
function userReducer(state = {}, action) {
switch (action.type) {
case ‘USER_UPDATE_NAME’:
// 返回新对象,而不是修改原对象
return {
…state,
name: action.payload.name
};
default:
return state;
}
}
1.2 Redux 核心概念详解
Action
Action 是改变状态的唯一方式,它是一个普通的 JavaScript 对象,必须有一个
type
字段来描述发生的动作。
// 同步 Action
const incrementCounter = () => ({
type: ‘INCREMENT_COUNTER’
});
// 异步 Action(使用 Redux Thunk)
const fetchUserData = (userId) => {
return async (dispatch) => {
dispatch({ type: ‘USER_DATA_LOADING’ });
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
dispatch({
type: ‘USER_DATA_SUCCESS’,
payload: data
});
} catch (error) {
dispatch({
type: ‘USER_DATA_ERROR’,
payload: error.message
});
}
};
};
// Action Types 常量(最佳实践)
export const ActionTypes = {
INCREMENT: ‘INCREMENT’,
DECREMENT: ‘DECREMENT’,
SET_COUNT: ‘SET_COUNT’
};
Reducer
Reducer 是纯函数,接收当前状态和 action,返回新的状态。
// 计数器 Reducer
const counterReducer = (state = 0, action) => {
switch (action.type) {
case ‘INCREMENT’:
return state + 1;
case ‘DECREMENT’:
return state – 1;
case ‘SET_COUNT’:
return action.payload;
default:
return state;
}
};
// 复杂的状态结构 Reducer
const initialState = {
items: [],
loading: false,
error: null,
pagination: {
page: 1,
pageSize: 10,
total: 0
}
};
const itemsReducer = (state = initialState, action) => {
switch (action.type) {
case ‘FETCH_ITEMS_START’:
return {
…state,
loading: true,
error: null
};
case ‘FETCH_ITEMS_SUCCESS’:
return {
…state,
loading: false,
items: action.payload.items,
pagination: {
…state.pagination,
total: action.payload.total
}
};
case ‘FETCH_ITEMS_FAILURE’:
return {
…state,
loading: false,
error: action.payload
};
default:
return state;
}
};
Store
Store 是 Redux 的核心,它持有应用的全部状态,提供获取状态、订阅状态变化和分发 action 的方法。
import { createStore, applyMiddleware, compose } from ‘redux’;
import thunk from ‘redux-thunk’;
import rootReducer from ‘./reducers’;
// 创建 Store
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
// Store 的基本使用
console.log(‘初始状态:’, store.getState());
// 订阅状态变化
const unsubscribe = store.subscribe(() => {
console.log(‘状态更新:’, store.getState());
});
// 分发 Action
store.dispatch({ type: ‘INCREMENT’ });
// 取消订阅
unsubscribe();
第二部分:Redux 实战项目搭建
2.1 项目初始化与配置
让我们创建一个完整的博客应用项目,从零开始搭建 Redux 环境。
步骤 1:创建项目结构
blog-app/
├── src/
│ ├── components/
│ │ ├── PostList.js
│ │ ├── PostForm.js
│ │ └── PostItem.js
│ ├── store/
│ │ ├── actions/
│ │ │ ├── postActions.js
│ │ │ └── userActions.js
│ │ ├── reducers/
│ │ │ ├── postReducer.js
│ │ │ ├── userReducer.js
│ │ │ └── index.js
│ │ ├── selectors/
│ │ │ └── postSelectors.js
│ │ └── store.js
│ ├── App.js
│ └── index.js
├── package.json
└── README.md
步骤 2:安装依赖
npm install redux react-redux redux-thunk redux-devtools-extension
步骤 3:配置 Store
// src/store/store.js
import { createStore, applyMiddleware, compose } from ‘redux’;
import thunk from ‘redux-thunk’;
import rootReducer from ‘./reducers’;
// 开发环境启用 Redux DevTools
const composeEnhancers =
typeof window === ‘object’ && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
: compose;
const store = createStore(
rootReducer,
composeEnhancers(applyMiddleware(thunk))
);
export default store;
2.2 完整的博客应用状态设计
状态结构设计
// 完整的应用状态结构
const appState = {
// 用户状态
user: {
currentUser: null,
isAuthenticated: false,
loading: false,
error: null
},
// 文章状态
posts: {
items: [],
currentPost: null,
loading: false,
error: null,
pagination: {
page: 1,
pageSize: 10,
total: 0
}
},
// 评论状态
comments: {
byPostId: {}, // 按文章ID索引的评论
loading: false,
error: null
},
// UI 状态
ui: {
sidebarOpen: true,
theme: ‘light’,
modals: {
createPost: false,
editPost: false,
deleteConfirm: false
}
}
};
Action Types 定义
// src/store/actions/types.js
export const POSTActionTypes = {
FETCH_POSTS_START: ‘POST/FETCH_POSTS_START’,
FETCH_POSTS_SUCCESS: ‘POST/FETCH_POSTS_SUCCESS’,
FETCH_POSTS_FAILURE: ‘POST/FETCH_POSTS_FAILURE’,
FETCH_POST_START: ‘POST/FETCH_POST_START’,
FETCH_POST_SUCCESS: ‘POST/FETCH_POST_SUCCESS’,
FETCH_POST_FAILURE: ‘POST/FETCH_POST_FAILURE’,
CREATE_POST_START: ‘POST/CREATE_POST_START’,
CREATE_POST_SUCCESS: ‘POST/CREATE_POST_SUCCESS’,
CREATE_POST_FAILURE: ‘POST/CREATE_POST_FAILURE’,
UPDATE_POST_START: ‘POST/UPDATE_POST_START’,
UPDATE_POST_SUCCESS: ‘POST/UPDATE_POST_SUCCESS’,
UPDATE_POST_FAILURE: ‘POST/UPDATE_POST_FAILURE’,
DELETE_POST_START: ‘POST/DELETE_POST_START’,
DELETE_POST_SUCCESS: ‘POST/DELETE_POST_SUCCESS’,
DELETE_POST_FAILURE: ‘POST/DELETE_POST_FAILURE’
};
export const USERActionTypes = {
LOGIN_START: ‘USER/LOGIN_START’,
LOGIN_SUCCESS: ‘USER/LOGIN_SUCCESS’,
LOGIN_FAILURE: ‘USER/LOGIN_FAILURE’,
LOGOUT: ‘USER/LOGOUT’,
REGISTER_START: ‘USER/REGISTER_START’,
REGISTER_SUCCESS: ‘USER/REGISTER_SUCCESS’,
REGISTER_FAILURE: ‘USER/REGISTER_FAILURE’
};
export const UIActionTypes = {
TOGGLE_SIDEBAR: ‘UI/TOGGLE_SIDEBAR’,
SET_THEME: ‘UI/SET_THEME’,
OPEN_MODAL: ‘UI/OPEN_MODAL’,
CLOSE_MODAL: ‘UI/CLOSE_MODAL’
};
2.3 Reducers 实现
// src/store/reducers/postReducer.js
import { POSTActionTypes } from ‘../actions/types’;
const initialState = {
items: [],
currentPost: null,
loading: false,
error: null,
pagination: {
page: 1,
pageSize: 10,
total: 0
}
};
const postReducer = (state = initialState, action) => {
switch (action.type) {
case POSTActionTypes.FETCH_POSTS_START:
return {
…state,
loading: true,
error: null
};
case POSTActionTypes.FETCH_POSTS_SUCCESS:
return {
…state,
loading: false,
items: action.payload.posts,
pagination: {
…state.pagination,
total: action.payload.total
}
};
case POSTActionTypes.FETCH_POSTS_FAILURE:
return {
…state,
loading: false,
error: action.payload
};
case POSTActionTypes.FETCH_POST_SUCCESS:
return {
…state,
loading: false,
currentPost: action.payload
};
case POSTActionTypes.CREATE_POST_SUCCESS:
return {
…state,
items: [action.payload, …state.items],
pagination: {
…state.pagination,
total: state.pagination.total + 1
}
};
case POSTActionTypes.UPDATE_POST_SUCCESS:
return {
…state,
items: state.items.map(post =>
post.id === action.payload.id ? action.payload : post
),
currentPost: action.payload.id === state.currentPost?.id
? action.payload
: state.currentPost
};
case POSTActionTypes.DELETE_POST_SUCCESS:
return {
…state,
items: state.items.filter(post => post.id !== action.payload),
pagination: {
…state.pagination,
total: state.pagination.total – 1
},
currentPost: state.currentPost?.id === action.payload ? null : state.currentPost
};
default:
return state;
}
};
export default postReducer;
// src/store/reducers/userReducer.js
import { USERActionTypes } from ‘../actions/types’;
const initialState = {
currentUser: null,
isAuthenticated: false,
loading: false,
error: null
};
const userReducer = (state = initialState, action) => {
switch (action.type) {
case USERActionTypes.LOGIN_START:
case USERActionTypes.REGISTER_START:
return {
…state,
loading: true,
error: null
};
case USERActionTypes.LOGIN_SUCCESS:
case USERActionTypes.REGISTER_SUCCESS:
return {
…state,
loading: false,
isAuthenticated: true,
currentUser: action.payload
};
case USERActionTypes.LOGIN_FAILURE:
case USERActionTypes.REGISTER_FAILURE:
return {
…state,
loading: false,
error: action.payload
};
case USERActionTypes.LOGOUT:
return {
…state,
currentUser: null,
isAuthenticated: false
};
default:
return state;
}
};
export default userReducer;
// src/store/reducers/uiReducer.js
import { UIActionTypes } from ‘../actions/types’;
const initialState = {
sidebarOpen: true,
theme: ‘light’,
modals: {
createPost: false,
editPost: false,
deleteConfirm: false
}
};
const uiReducer = (state = initialState, action) => {
switch (action.type) {
case UIActionTypes.TOGGLE_SIDEBAR:
return {
…state,
sidebarOpen: !state.sidebarOpen
};
case UIActionTypes.SET_THEME:
return {
…state,
theme: action.payload
};
case UIActionTypes.OPEN_MODAL:
return {
…state,
modals: {
…state.modals,
[action.payload]: true
}
};
case UIActionTypes.CLOSE_MODAL:
return {
…state,
modals: {
…state.modals,
[action.payload]: false
}
};
default:
return state;
}
};
export default uiReducer;
// src/store/reducers/index.js
import { combineReducers } from ‘redux’;
import postReducer from ‘./postReducer’;
import userReducer from ‘./userReducer’;
import uiReducer from ‘./uiReducer’;
const rootReducer = combineReducers({
posts: postReducer,
user: userReducer,
ui: uiReducer
});
export default rootReducer;
2.4 Action Creators 实现
// src/store/actions/postActions.js
import { POSTActionTypes } from ‘./types’;
import api from ‘../../services/api’; // 假设的 API 服务
// 获取文章列表
export const fetchPosts = (page = 1, pageSize = 10) => {
return async (dispatch) => {
dispatch({ type: POSTActionTypes.FETCH_POSTS_START });
try {
const response = await api.get(`/posts?page=${page}&pageSize=${pageSize}`);
dispatch({
type: POSTActionTypes.FETCH_POSTS_SUCCESS,
payload: {
posts: response.data,
total: response.total
}
});
} catch (error) {
dispatch({
type: POSTActionTypes.FETCH_POSTS_FAILURE,
payload: error.message
});
}
};
};
// 获取单篇文章
export const fetchPost = (id) => {
return async (dispatch) => {
dispatch({ type: POSTActionTypes.FETCH_POST_START });
try {
const response = await api.get(`/posts/${id}`);
dispatch({
type: POSTActionTypes.FETCH_POST_SUCCESS,
payload: response.data
});
} catch (error) {
dispatch({
type: POSTActionTypes.FETCH_POST_FAILURE,
payload: error.message
});
}
};
};
// 创建文章
export const createPost = (postData) => {
return async (dispatch, getState) => {
// 可以访问整个状态树
const { user } = getState();
if (!user.isAuthenticated) {
return dispatch({
type: POSTActionTypes.CREATE_POST_FAILURE,
payload: ‘用户未登录’
});
}
dispatch({ type: POSTActionTypes.CREATE_POST_START });
try {
const response = await api.post(‘/posts’, {
…postData,
authorId: user.currentUser.id
});
dispatch({
type: POSTActionTypes.CREATE_POST_SUCCESS,
payload: response.data
});
return response.data; // 返回创建的数据供组件使用
} catch (error) {
dispatch({
type: POSTActionTypes.CREATE_POST_FAILURE,
payload: error.message
});
throw error;
}
};
};
// 更新文章
export const updatePost = (id, postData) => {
return async (dispatch) => {
dispatch({ type: POSTActionTypes.UPDATE_POST_START });
try {
const response = await api.put(`/posts/${id}`, postData);
dispatch({
type: POSTActionTypes.UPDATE_POST_SUCCESS,
payload: response.data
});
} catch (error) {
dispatch({
type: POSTActionTypes.UPDATE_POST_FAILURE,
payload: error.message
});
}
};
};
// 删除文章
export const deletePost = (id) => {
return async (dispatch) => {
dispatch({ type: POSTActionTypes.DELETE_POST_START });
try {
await api.delete(`/posts/${id}`);
dispatch({
type: POSTActionTypes.DELETE_POST_SUCCESS,
payload: id
});
} catch (error) {
dispatch({
type: POSTActionTypes.DELETE_POST_FAILURE,
payload: error.message
});
}
};
};
第三部分:React 组件集成
3.1 Provider 与 connect 的使用
// src/index.js
import React from ‘react’;
import ReactDOM from ‘react-dom’;
import { Provider } from ‘react-redux’;
import store from ‘./store/store’;
import App from ‘./App’;
ReactDOM.render(
document.getElementById(‘root’)
);
使用 connect 连接组件
// src/components/PostList.js
import React, { useEffect } from ‘react’;
import { connect } from ‘react-redux’;
import { fetchPosts, deletePost } from ‘../store/actions/postActions’;
import { openModal } from ‘../store/actions/uiActions’;
import PostItem from ‘./PostItem’;
const PostList = ({
posts,
loading,
error,
fetchPosts,
deletePost,
openModal,
isAuthenticated
}) => {
useEffect(() => {
fetchPosts();
}, [fetchPosts]);
if (loading) return
;
if (error) return
;
return (
文章列表
{isAuthenticated && (
)}
{posts.map(post => (
))}
);
};
// 将状态映射到组件的 props
const mapStateToProps = (state) => ({
posts: state.posts.items,
loading: state.posts.loading,
error: state.posts.error,
isAuthenticated: state.user.isAuthenticated
});
// 将 action creators 映射到组件的 props
const mapDispatchToProps = {
fetchPosts,
deletePost,
openModal
};
export default connect(mapStateToProps, mapDispatchToProps)(PostList);
3.2 使用 Hooks API(推荐方式)
React-Redux 提供了
useSelector
和
useDispatch
hooks,这是现代 Redux 开发的推荐方式。
// src/components/PostListWithHooks.js
import React, { useEffect, useCallback } from ‘react’;
import { useSelector, useDispatch } from ‘react-redux’;
import { fetchPosts, deletePost } from ‘../store/actions/postActions’;
import { openModal } from ‘../store/actions/uiActions’;
import PostItem from ‘./PostItem’;
const PostListWithHooks = () => {
const dispatch = useDispatch();
// 使用 useSelector 获取状态
const posts = useSelector(state => state.posts.items);
const loading = useSelector(state => state.posts.loading);
const error = useSelector(state => state.posts.error);
const isAuthenticated = useSelector(state => state.user.isAuthenticated);
// 使用 useCallback 优化性能
const handleFetchPosts = useCallback(() => {
dispatch(fetchPosts());
}, [dispatch]);
const handleDeletePost = useCallback((id) => {
if (window.confirm(‘确定要删除这篇文章吗?’)) {
dispatch(deletePost(id));
}
}, [dispatch]);
const handleOpenCreateModal = useCallback(() => {
dispatch(openModal(‘createPost’));
}, [dispatch]);
useEffect(() => {
handleFetchPosts();
}, [handleFetchPosts]);
if (loading) return
;
if (error) return
;
return (
文章列表
{isAuthenticated && (
)}
{posts.map(post => (
))}
);
};
export default PostListWithHooks;
3.3 异步操作与表单处理
// src/components/PostForm.js
import React, { useState, useEffect } from ‘react’;
import { useSelector, useDispatch } from ‘react-redux’;
import { createPost, updatePost } from ‘../store/actions/postActions’;
import { closeModal } from ‘../store/actions/uiActions’;
const PostForm = ({ postId }) => {
const dispatch = useDispatch();
const currentPost = useSelector(state =>
state.posts.items.find(p => p.id === postId) || state.posts.currentPost
);
const loading = useSelector(state => state.posts.loading);
const [formData, setFormData] = useState({
title: ”,
content: ”,
tags: ”
});
useEffect(() => {
if (currentPost) {
setFormData({
title: currentPost.title,
content: currentPost.content,
tags: currentPost.tags?.join(‘, ‘) || ”
});
}
}, [currentPost]);
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
…prev,
[name]: value
}));
};
const handleSubmit = async (e) => {
e.preventDefault();
const submitData = {
…formData,
tags: formData.tags.split(‘,’).map(tag => tag.trim()).filter(Boolean)
};
try {
if (postId) {
await dispatch(updatePost(postId, submitData));
} else {
await dispatch(createPost(submitData));
}
dispatch(closeModal(‘createPost’));
dispatch(closeModal(‘editPost’));
setFormData({ title: ”, content: ”, tags: ” });
} catch (error) {
// 错误已在 action 中处理,这里可以显示用户友好的消息
console.error(‘提交失败:’, error);
}
};
return (
);
};
export default PostForm;
第四部分:高级应用与性能优化
4.1 使用 Reselect 创建记忆化选择器
// src/store/selectors/postSelectors.js
import { createSelector } from ‘reselect’;
// 基础选择器
const getPosts = state => state.posts.items;
const getSearchTerm = (state, props) => props.searchTerm || ”;
const getUserId = state => state.user.currentUser?.id;
// 记忆化选择器:过滤文章
export const getFilteredPosts = createSelector(
[getPosts, getSearchTerm],
(posts, searchTerm) => {
if (!searchTerm) return posts;
return posts.filter(post =>
post.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
post.content.toLowerCase().includes(searchTerm.toLowerCase()) ||
post.tags?.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase()))
);
}
);
// 记忆化选择器:获取用户文章
export const getUserPosts = createSelector(
[getPosts, getUserId],
(posts, userId) => {
if (!userId) return [];
return posts.filter(post => post.authorId === userId);
}
);
// 记忆化选择器:获取热门文章(有评论的文章)
export const getPopularPosts = createSelector(
[getPosts],
(posts) => {
return posts
.filter(post => (post.commentCount || 0) > 5)
.sort((a, b) => (b.commentCount || 0) – (a.commentCount || 0));
}
);
// 记忆化选择器:文章统计
export const getPostsStats = createSelector(
[getPosts],
(posts) => {
return {
total: posts.length,
withComments: posts.filter(p => p.commentCount > 0).length,
avgComments: posts.reduce((sum, p) => sum + (p.commentCount || 0), 0) / posts.length || 0
};
}
);
在组件中使用选择器
// src/components/Dashboard.js
import React from ‘react’;
import { useSelector } from ‘react-redux’;
import {
getFilteredPosts,
getUserPosts,
getPopularPosts,
getPostsStats
} from ‘../store/selectors/postSelectors’;
const Dashboard = ({ searchTerm }) => {
// 使用记忆化选择器,只有当相关状态变化时才会重新计算
const filteredPosts = useSelector(state =>
getFilteredPosts(state, { searchTerm })
);
const userPosts = useSelector(getUserPosts);
const popularPosts = useSelector(getPopularPosts);
const stats = useSelector(getPostsStats);
return (
统计信息
总文章数: {stats.total}
有评论的文章: {stats.withComments}
平均评论数: {stats.avgComments.toFixed(1)}
我的文章 ({userPosts.length})
{userPosts.map(post => (
))}
热门文章 ({popularPosts.length})
{popularPosts.map(post => (
))}
搜索结果 ({filteredPosts.length})
{filteredPosts.map(post => (
))}
);
};
export default Dashboard;
4.2 中间件高级应用
// src/store/middlewares/logger.js
// 自定义日志中间件
const logger = store => next => action => {
console.group(action.type);
console.log(‘当前状态:’, store.getState());
console.log(‘动作:’, action);
const result = next(action);
console.log(‘下一个状态:’, store.getState());
console.groupEnd();
return result;
};
// src/store/middlewares/analytics.js
// 分析中间件 – 记录用户行为
const analytics = store => next => action => {
// 在 action 处理前记录
const startTime = Date.now();
const result = next(action);
// 在 action 处理后记录
const endTime = Date.now();
const duration = endTime – startTime;
// 发送到分析服务(示例)
if (window.analytics && action.type.startsWith(‘POST/’)) {
window.analytics.track(‘Redux Action’, {
type: action.type,
duration,
timestamp: new Date().toISOString()
});
}
return result;
};
// src/store/middlewares/errorHandler.js
// 错误处理中间件
const errorHandler = store => next => action => {
try {
return next(action);
} catch (error) {
console.error(‘Action 处理出错:’, error);
// 可以在这里发送错误到监控服务
if (window.Sentry) {
window.Sentry.captureException(error, {
extra: { action }
});
}
// 可以 dispatch 一个错误 action
store.dispatch({
type: ‘ERROR_OCCURRED’,
payload: {
message: error.message,
action: action.type
}
});
throw error;
}
};
// src/store/store.js – 应用中间件
import { createStore, applyMiddleware, compose } from ‘redux’;
import thunk from ‘redux-thunk’;
import rootReducer from ‘./reducers’;
import logger from ‘./middlewares/logger’;
import analytics from ‘./middlewares/analytics’;
import errorHandler from ‘./middlewares/errorHandler’;
const composeEnhancers =
typeof window === ‘object’ && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
: compose;
const store = createStore(
rootReducer,
composeEnhancers(
applyMiddleware(
thunk,
logger,
analytics,
errorHandler
)
)
);
export default store;
4.3 Redux Toolkit 高级应用
Redux Toolkit 是 Redux 官方推荐的现代开发方式,大大简化了 Redux 的使用。
npm install @reduxjs/toolkit
// src/store/slices/postSlice.js
import { createSlice, createAsyncThunk } from ‘@reduxjs/toolkit’;
import api from ‘../../services/api’;
// 创建异步 thunk
export const fetchPosts = createAsyncThunk(
‘posts/fetchPosts’,
async ({ page = 1, pageSize = 10 }, { rejectWithValue }) => {
try {
const response = await api.get(`/posts?page=${page}&pageSize=${pageSize}`);
return {
posts: response.data,
total: response.total
};
} catch (error) {
return rejectWithValue(error.message);
}
}
);
export const createPost = createAsyncThunk(
‘posts/createPost’,
async (postData, { getState, rejectWithValue }) => {
try {
const state = getState();
const response = await api.post(‘/posts’, {
…postData,
authorId: state.user.currentUser.id
});
return response.data;
} catch (error) {
return rejectWithValue(error.message);
}
}
);
// 创建 slice
const postSlice = createSlice({
name: ‘posts’,
initialState: {
items: [],
currentPost: null,
loading: false,
error: null,
pagination: {
page: 1,
pageSize: 10,
total: 0
}
},
reducers: {
// 同步 reducers
setCurrentPost: (state, action) => {
state.currentPost = action.payload;
},
clearError: (state) => {
state.error = null;
}
},
extraReducers: (builder) => {
builder
// fetchPosts
.addCase(fetchPosts.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchPosts.fulfilled, (state, action) => {
state.loading = false;
state.items = action.payload.posts;
state.pagination.total = action.payload.total;
})
.addCase(fetchPosts.rejected, (state, action) => {
state.loading = false;
state.error = action.payload;
})
// createPost
.addCase(createPost.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(createPost.fulfilled, (state, action) => {
state.loading = false;
state.items.unshift(action.payload);
state.pagination.total += 1;
})
.addCase(createPost.rejected, (state, action) => {
state.loading = false;
state.error = action.payload;
});
}
});
export const { setCurrentPost, clearError } = postSlice.actions;
export default postSlice.reducer;
// src/store/slices/userSlice.js
import { createSlice, createAsyncThunk } from ‘@reduxjs/toolkit’;
import api from ‘../../services/api’;
export const login = createAsyncThunk(
‘user/login’,
async (credentials, { rejectWithValue }) => {
try {
const response = await api.post(‘/auth/login’, credentials);
// 保存 token 到 localStorage
localStorage.setItem(‘token’, response.token);
return response.user;
} catch (error) {
return rejectWithValue(error.message);
}
}
);
export const logout = createAsyncThunk(
‘user/logout’,
async (_, { rejectWithValue }) => {
try {
await api.post(‘/auth/logout’);
localStorage.removeItem(‘token’);
return null;
} catch (error) {
return rejectWithValue(error.message);
}
}
);
const userSlice = createSlice({
name: ‘user’,
initialState: {
currentUser: null,
isAuthenticated: false,
loading: false,
error: null
},
reducers: {
// 同步设置用户(例如从 localStorage 恢复)
setUser: (state, action) => {
state.currentUser = action.payload;
state.isAuthenticated = !!action.payload;
}
},
extraReducers: (builder) => {
builder
.addCase(login.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(login.fulfilled, (state, action) => {
state.loading = false;
state.currentUser = action.payload;
state.isAuthenticated = true;
})
.addCase(login.rejected, (state, action) => {
state.loading = false;
state.error = action.payload;
})
.addCase(logout.fulfilled, (state) => {
state.currentUser = null;
state.isAuthenticated = false;
});
}
});
export const { setUser } = userSlice.actions;
export default userSlice.reducer;
// src/store/store.js – 使用 Redux Toolkit
import { configureStore } from ‘@reduxjs/toolkit’;
import postReducer from ‘./slices/postSlice’;
import userReducer from ‘./slices/userSlice’;
import uiReducer from ‘./slices/uiSlice’;
export const store = configureStore({
reducer: {
posts: postReducer,
user: userReducer,
ui: uiReducer
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [‘persist/PERSIST’, ‘persist/REHYDRATE’]
}
})
});
export default store;
4.4 持久化与状态恢复
// src/store/middlewares/persistence.js
import { loadState, saveState } from ‘../../utils/storage’;
// 持久化中间件
const persistenceMiddleware = store => next => action => {
// 先执行 action
const result = next(action);
// 保存特定的状态到 localStorage
const state = store.getState();
const stateToPersist = {
user: state.user,
ui: state.ui
};
saveState(stateToPersist);
return result;
};
// 使用 redux-persist(更完整的解决方案)
import { persistStore, persistReducer } from ‘redux-persist’;
import storage from ‘redux-persist/lib/storage’; // localStorage
const persistConfig = {
key: ‘root’,
storage,
whitelist: [‘user’, ‘ui’], // 只持久化这些 reducer
blacklist: [‘posts’] // 不持久化 posts
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
// 在 store.js 中
import { persistStore } from ‘redux-persist’;
export const store = createStore(
persistedReducer,
composeEnhancers(applyMiddleware(thunk))
);
export const persistor = persistStore(store);
// 在 App.js 中
import { PersistGate } from ‘redux-persist/integration/react’;
function App() {
return (