// Redux 三大原则:
// 1. 单一数据源
// 2. State 是只读的
// 3. 使用纯函数修改
// store.js
import { configureStore, createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
// 1. 创建 Slice
const counterSlice = createSlice({
name: "counter",
initialState: {
value: 0,
status: "idle", // idle, loading, succeeded, failed
error: null,
},
reducers: {
increment: state => {
state.value += 1;
},
decrement: state => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
reset: state => {
state.value = 0;
state.status = "idle";
state.error = null;
},
},
extraReducers: builder => {
builder
.addCase(fetchCount.pending, state => {
state.status = "loading";
})
.addCase(fetchCount.fulfilled, (state, action) => {
state.status = "succeeded";
state.value = action.payload;
})
.addCase(fetchCount.rejected, (state, action) => {
state.status = "failed";
state.error = action.error.message;
});
},
});
// 2. 创建异步 Thunk
export const fetchCount = createAsyncThunk(
"counter/fetchCount",
async (amount, { rejectWithValue }) => {
try {
// 模拟 API 调用
const response = await new Promise(resolve => {
setTimeout(() => {
resolve({ data: amount });
}, 1000);
});
return response.data;
} catch (error) {
return rejectWithValue("获取数据失败");
}
}
);
// 3. 创建 API Slice (RTK Query)
export const api = createApi({
reducerPath: "api",
baseQuery: fetchBaseQuery({
baseUrl: "https://jsonplaceholder.typicode.com/",
prepareHeaders: headers => {
// 添加认证头等
headers.set("Authorization", `Bearer ${localStorage.getItem("token")}`);
return headers;
},
}),
tagTypes: ["Post", "User"], // 用于缓存失效
endpoints: builder => ({
getPosts: builder.query({
query: (page = 1) => `posts?_page=${page}&_limit=10`,
providesTags: ["Post"],
}),
getPost: builder.query({
query: id => `posts/${id}`,
providesTags: (result, error, id) => [{ type: "Post", id }],
}),
createPost: builder.mutation({
query: newPost => ({
url: "posts",
method: "POST",
body: newPost,
}),
invalidatesTags: ["Post"],
}),
updatePost: builder.mutation({
query: ({ id, ...updates }) => ({
url: `posts/${id}`,
method: "PUT",
body: updates,
}),
invalidatesTags: (result, error, { id }) => [{ type: "Post", id }],
}),
deletePost: builder.mutation({
query: id => ({
url: `posts/${id}`,
method: "DELETE",
}),
invalidatesTags: ["Post"],
}),
}),
});
// 4. 用户 Slice(复杂示例)
const userSlice = createSlice({
name: "user",
initialState: {
currentUser: null,
users: [],
pagination: {
page: 1,
limit: 10,
total: 0,
pages: 0,
},
filters: {
role: "all",
status: "active",
search: "",
},
loading: false,
error: null,
},
reducers: {
setCurrentUser: (state, action) => {
state.currentUser = action.payload;
},
setUsers: (state, action) => {
state.users = action.payload.items;
state.pagination = action.payload.pagination;
},
setFilter: (state, action) => {
state.filters = { ...state.filters, ...action.payload };
},
updateUser: (state, action) => {
const index = state.users.findIndex(user => user.id === action.payload.id);
if (index !== -1) {
state.users[index] = { ...state.users[index], ...action.payload };
}
},
deleteUser: (state, action) => {
state.users = state.users.filter(user => user.id !== action.payload);
},
setLoading: (state, action) => {
state.loading = action.payload;
},
setError: (state, action) => {
state.error = action.payload;
},
resetUsers: state => {
state.users = [];
state.pagination = { page: 1, limit: 10, total: 0, pages: 0 };
state.filters = { role: "all", status: "active", search: "" };
},
},
});
// 5. 组合 Reducer
const rootReducer = {
counter: counterSlice.reducer,
user: userSlice.reducer,
[api.reducerPath]: api.reducer,
};
// 6. 创建 Store
export const store = configureStore({
reducer: rootReducer,
middleware: getDefaultMiddleware =>
getDefaultMiddleware({
serializableCheck: {
// 忽略一些非序列化值
ignoredActions: ["user/setCurrentUser"],
ignoredPaths: ["user.currentUser.avatar"],
},
}).concat(api.middleware),
devTools: process.env.NODE_ENV !== "production",
// 预加载状态
preloadedState: {
counter: { value: 100 }, // 从 localStorage 或服务器加载
},
// 增强器
enhancers: defaultEnhancers => [
...defaultEnhancers,
// 可以添加更多增强器
],
});
// 导出 Actions
export const { increment, decrement, incrementByAmount, reset } = counterSlice.actions;
export const {
setCurrentUser,
setUsers,
setFilter,
updateUser,
deleteUser,
setLoading,
setError,
resetUsers,
} = userSlice.actions;
// 导出 Hooks
export const {
useGetPostsQuery,
useGetPostQuery,
useCreatePostMutation,
useUpdatePostMutation,
useDeletePostMutation,
} = api;
// App.jsx
import { Provider } from "react-redux";
import { store } from "./store";
function App() {
return (
<Provider store={store}>
<div className="app">
<CounterDemo />
<UserManager />
<PostManager />
<ShoppingCart />
<RealTimeUpdates />
</div>
</Provider>
);
}
// CounterDemo.jsx
import { useSelector, useDispatch } from "react-redux";
import { increment, decrement, incrementByAmount, reset, fetchCount } from "./store";
function CounterDemo() {
const dispatch = useDispatch();
const counter = useSelector(state => state.counter);
const [inputValue, setInputValue] = useState("");
const handleIncrementAsync = async () => {
try {
await dispatch(fetchCount(5)).unwrap();
// unwrap() 会返回 Promise,可以正确处理成功/失败
} catch (error) {
console.error("异步操作失败:", error);
}
};
return (
<div className="counter-demo">
<h2>计数器演示</h2>
<div className="counter-display">
<span>当前值: {counter.value}</span>
<span className={`status ${counter.status}`}>
{counter.status === "loading" && "加载中..."}
{counter.error && `错误: ${counter.error}`}
</span>
</div>
<div className="controls">
<button onClick={() => dispatch(increment())}>增加</button>
<button onClick={() => dispatch(decrement())}>减少</button>
<div className="custom-increment">
<input
type="number"
value={inputValue}
onChange={e => setInputValue(e.target.value)}
placeholder="输入增加值"
/>
<button
onClick={() => {
dispatch(incrementByAmount(Number(inputValue) || 0));
setInputValue("");
}}
disabled={counter.status === "loading"}
>
增加指定值
</button>
</div>
<button onClick={handleIncrementAsync} disabled={counter.status === "loading"}>
异步增加5
</button>
<button onClick={() => dispatch(reset())} className="reset-btn">
重置
</button>
</div>
<div className="history-log">
<h3>操作历史</h3>
{/* 可以添加历史记录功能 */}
</div>
</div>
);
}
// UserManager.jsx
import { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { setUsers, setFilter, updateUser, deleteUser, setLoading, setError } from "./store";
function UserManager() {
const dispatch = useDispatch();
const { users, pagination, filters, loading, error } = useSelector(state => state.user);
const [editingUser, setEditingUser] = useState(null);
const [formData, setFormData] = useState({ name: "", email: "", role: "user" });
// 获取用户列表
const fetchUsers = async () => {
dispatch(setLoading(true));
try {
const queryParams = new URLSearchParams({
page: pagination.page,
limit: pagination.limit,
...filters,
}).toString();
const response = await fetch(`/api/users?${queryParams}`);
const data = await response.json();
dispatch(setUsers(data));
} catch (err) {
dispatch(setError("获取用户列表失败"));
} finally {
dispatch(setLoading(false));
}
};
// 处理筛选
const handleFilterChange = newFilters => {
dispatch(setFilter(newFilters));
// 防抖获取数据
clearTimeout(filterTimeout);
filterTimeout = setTimeout(fetchUsers, 300);
};
// 分页
const handlePageChange = newPage => {
dispatch(setFilter({ page: newPage }));
fetchUsers();
};
// 编辑用户
const handleEditUser = user => {
setEditingUser(user.id);
setFormData(user);
};
const handleSaveUser = async () => {
try {
await dispatch(updateUser(formData)).unwrap();
setEditingUser(null);
} catch (error) {
console.error("更新用户失败:", error);
}
};
// 删除用户
const handleDeleteUser = async userId => {
if (window.confirm("确定删除此用户吗?")) {
try {
await dispatch(deleteUser(userId)).unwrap();
} catch (error) {
console.error("删除用户失败:", error);
}
}
};
useEffect(() => {
fetchUsers();
}, []);
return (
<div className="user-manager">
<h2>用户管理</h2>
{/* 筛选器 */}
<div className="filters">
<input
type="text"
placeholder="搜索用户..."
value={filters.search}
onChange={e => handleFilterChange({ search: e.target.value })}
/>
<select
value={filters.role}
onChange={e => handleFilterChange({ role: e.target.value })}
>
<option value="all">所有角色</option>
<option value="admin">管理员</option>
<option value="user">普通用户</option>
</select>
</div>
{/* 加载状态 */}
{loading && <div className="loading">加载中...</div>}
{error && <div className="error">{error}</div>}
{/* 用户列表 */}
<div className="user-list">
{users.map(user => (
<div key={user.id} className="user-card">
{editingUser === user.id ? (
<div className="edit-form">
<input
type="text"
value={formData.name}
onChange={e =>
setFormData({ ...formData, name: e.target.value })
}
/>
<input
type="email"
value={formData.email}
onChange={e =>
setFormData({ ...formData, email: e.target.value })
}
/>
<button onClick={handleSaveUser}>保存</button>
<button onClick={() => setEditingUser(null)}>取消</button>
</div>
) : (
<>
<div className="user-info">
<h4>{user.name}</h4>
<p>{user.email}</p>
<span className={`role ${user.role}`}>{user.role}</span>
</div>
<div className="user-actions">
<button onClick={() => handleEditUser(user)}>编辑</button>
<button
onClick={() => handleDeleteUser(user.id)}
className="delete-btn"
>
删除
</button>
</div>
</>
)}
</div>
))}
</div>
{/* 分页 */}
<div className="pagination">
<button
onClick={() => handlePageChange(pagination.page - 1)}
disabled={pagination.page === 1}
>
上一页
</button>
<span>
第 {pagination.page} 页 / 共 {pagination.pages} 页
</span>
<button
onClick={() => handlePageChange(pagination.page + 1)}
disabled={pagination.page === pagination.pages}
>
下一页
</button>
</div>
</div>
);
}
// PostManager.jsx - 使用 RTK Query
import {
useGetPostsQuery,
useCreatePostMutation,
useUpdatePostMutation,
useDeletePostMutation,
} from "./store";
function PostManager() {
const [page, setPage] = useState(1);
const [newPost, setNewPost] = useState({ title: "", body: "" });
const [editingPost, setEditingPost] = useState(null);
// RTK Query hooks
const {
data: postsData,
isLoading,
isError,
error,
refetch,
} = useGetPostsQuery(page, {
// 配置选项
pollingInterval: 30000, // 30秒轮询
skip: false, // 是否跳过请求
refetchOnMountOrArgChange: true, // 参数变化时重新获取
refetchOnFocus: true, // 窗口聚焦时重新获取
refetchOnReconnect: true, // 网络重连时重新获取
});
const [createPost, { isLoading: isCreating }] = useCreatePostMutation();
const [updatePost, { isLoading: isUpdating }] = useUpdatePostMutation();
const [deletePost, { isLoading: isDeleting }] = useDeletePostMutation();
const handleCreatePost = async () => {
try {
await createPost(newPost).unwrap();
setNewPost({ title: "", body: "" });
} catch (error) {
console.error("创建文章失败:", error);
}
};
const handleUpdatePost = async (postId, updates) => {
try {
await updatePost({ id: postId, ...updates }).unwrap();
setEditingPost(null);
} catch (error) {
console.error("更新文章失败:", error);
}
};
const handleDeletePost = async postId => {
if (window.confirm("确定删除此文章吗?")) {
try {
await deletePost(postId).unwrap();
} catch (error) {
console.error("删除文章失败:", error);
}
}
};
return (
<div className="post-manager">
<h2>文章管理 (RTK Query)</h2>
{/* 创建新文章 */}
<div className="create-post">
<h3>创建新文章</h3>
<input
type="text"
placeholder="标题"
value={newPost.title}
onChange={e => setNewPost({ ...newPost, title: e.target.value })}
/>
<textarea
placeholder="内容"
value={newPost.body}
onChange={e => setNewPost({ ...newPost, body: e.target.value })}
/>
<button onClick={handleCreatePost} disabled={isCreating}>
{isCreating ? "创建中..." : "创建文章"}
</button>
</div>
{/* 文章列表 */}
<div className="post-list">
<h3>文章列表</h3>
{isLoading && <div className="loading">加载中...</div>}
{isError && <div className="error">错误: {error.message}</div>}
{postsData?.map(post => (
<div key={post.id} className="post-card">
{editingPost === post.id ? (
<div className="edit-form">
<input
type="text"
defaultValue={post.title}
ref={input => input && (input.value = post.title)}
/>
<textarea
defaultValue={post.body}
ref={textarea => textarea && (textarea.value = post.body)}
/>
<button
onClick={() =>
handleUpdatePost(post.id, {
title: "新标题", // 实际从表单获取
body: "新内容",
})
}
>
保存
</button>
<button onClick={() => setEditingPost(null)}>取消</button>
</div>
) : (
<>
<h4>{post.title}</h4>
<p>{post.body}</p>
<div className="post-actions">
<button onClick={() => setEditingPost(post.id)}>编辑</button>
<button
onClick={() => handleDeletePost(post.id)}
disabled={isDeleting}
>
删除
</button>
</div>
</>
)}
</div>
))}
</div>
{/* 分页控制 */}
<div className="pagination-controls">
<button onClick={() => setPage(page - 1)} disabled={page === 1}>
上一页
</button>
<span>第 {page} 页</span>
<button
onClick={() => setPage(page + 1)}
disabled={postsData && postsData.length < 10}
>
下一页
</button>
<button onClick={refetch} className="refresh-btn">
刷新数据
</button>
</div>
</div>
);
}
// middleware.js
import { createLogger } from "redux-logger";
import { thunk } from "redux-thunk";
import storage from "redux-persist/lib/storage";
import { persistStore, persistReducer } from "redux-persist";
// 自定义中间件
const apiMiddleware = store => next => action => {
if (action.type === "API_REQUEST") {
const { types, callAPI, shouldCallAPI = () => true, payload = {} } = action;
if (!types) {
throw new Error("缺少 types 参数");
}
if (!Array.isArray(types) || types.length !== 3) {
throw new Error("types 必须是包含三个字符串的数组");
}
if (typeof callAPI !== "function") {
throw new Error("callAPI 必须是函数");
}
if (!shouldCallAPI(store.getState())) {
return;
}
const [requestType, successType, failureType] = types;
store.dispatch({
type: requestType,
payload,
});
return callAPI().then(
response =>
store.dispatch({
type: successType,
payload: response,
meta: payload,
}),
error =>
store.dispatch({
type: failureType,
payload: error,
meta: payload,
error: true,
})
);
}
return next(action);
};
// 性能监控中间件
const performanceMiddleware = store => next => action => {
const start = performance.now();
const result = next(action);
const end = performance.now();
if (process.env.NODE_ENV === "development") {
console.log(`${action.type} 执行时间: ${(end - start).toFixed(2)}ms`);
}
return result;
};
// 持久化配置
const persistConfig = {
key: "root",
storage,
whitelist: ["user", "cart"], // 只持久化这些reducer
blacklist: ["counter"], // 不持久化这些reducer
migrate: state => {
// 版本迁移逻辑
return Promise.resolve(state);
},
serialize: false, // 是否序列化
deserialize: false, // 是否反序列化
};
// 组合中间件
const middleware = [
thunk,
apiMiddleware,
performanceMiddleware,
process.env.NODE_ENV === "development" &&
createLogger({
collapsed: true,
duration: true,
timestamp: true,
predicate: (getState, action) => !action.type.includes("@@redux-form"),
}),
].filter(Boolean);
// 应用持久化
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = configureStore({
reducer: persistedReducer,
middleware: getDefaultMiddleware =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}).concat(middleware),
});
export const persistor = persistStore(store);
// MobX 核心概念:
// 1. Observable State - 可观察的状态
// 2. Actions - 修改状态的动作
// 3. Computed Values - 计算值
// 4. Reactions - 响应状态变化的反应
// stores/counterStore.js
import { makeAutoObservable, action, computed, reaction, autorun } from "mobx";
class CounterStore {
constructor() {
this.value = 0;
this.history = [];
this.maxHistoryLength = 10;
makeAutoObservable(this, {
increment: action,
decrement: action,
reset: action,
double: computed,
canUndo: computed,
addToHistory: action.bound, // 绑定this
});
// 自动响应
this.setupReactions();
}
// Action - 增加
increment() {
this.value += 1;
this.addToHistory("INCREMENT");
}
// Action - 减少
decrement() {
this.value -= 1;
this.addToHistory("DECREMENT");
}
// Action - 增加指定值
incrementByAmount(amount) {
this.value += amount;
this.addToHistory(`INCREMENT_BY_${amount}`);
}
// Action - 重置
reset() {
this.value = 0;
this.history = [];
}
// Action - 添加到历史
addToHistory(actionType) {
this.history.push({
id: Date.now(),
action: actionType,
value: this.value,
timestamp: new Date().toISOString(),
});
// 保持历史记录长度
if (this.history.length > this.maxHistoryLength) {
this.history.shift();
}
}
// Action - 撤销
undo() {
if (this.history.length > 1) {
const lastState = this.history[this.history.length - 2];
this.value = lastState.value;
this.history.pop();
}
}
// Computed - 双倍值
get double() {
return this.value * 2;
}
// Computed - 是否可以撤销
get canUndo() {
return this.history.length > 1;
}
// Computed - 格式化显示
get displayValue() {
return `当前值: ${this.value}`;
}
// 设置反应
setupReactions() {
// 当值变化时执行
autorun(() => {
if (this.value > 10) {
console.log("值超过10了!");
}
});
// 当历史记录变化时执行
reaction(
() => this.history.length,
length => {
console.log(`历史记录数量: ${length}`);
}
);
// 监听特定变化
reaction(
() => this.value,
(value, previousValue) => {
if (value !== previousValue) {
console.log(`值从 ${previousValue} 变为 ${value}`);
}
}
);
}
// 异步 Action
async incrementAsync(amount) {
try {
// 模拟异步操作
await new Promise(resolve => setTimeout(resolve, 1000));
runInAction(() => {
this.incrementByAmount(amount);
});
} catch (error) {
console.error("异步操作失败:", error);
}
}
}
// stores/userStore.js
import { makeAutoObservable, flow } from "mobx";
class UserStore {
constructor() {
this.users = [];
this.currentUser = null;
this.loading = false;
this.error = null;
this.filters = {
role: "all",
search: "",
page: 1,
limit: 10,
};
this.pagination = {
total: 0,
pages: 0,
};
makeAutoObservable(this, {
fetchUsers: flow,
addUser: action,
updateUser: action,
deleteUser: action,
});
}
// Action - 设置筛选器
setFilter(key, value) {
this.filters[key] = value;
this.fetchUsers(); // 自动重新获取
}
// Generator函数处理异步 - flow
*fetchUsers() {
this.loading = true;
this.error = null;
try {
const queryParams = new URLSearchParams(this.filters).toString();
const response = yield fetch(`/api/users?${queryParams}`);
const data = yield response.json();
this.users = data.items;
this.pagination = data.pagination;
} catch (error) {
this.error = error.message;
} finally {
this.loading = false;
}
}
// Action - 添加用户
addUser(user) {
this.users.push({
id: Date.now(),
...user,
createdAt: new Date().toISOString(),
});
}
// Action - 更新用户
updateUser(id, updates) {
const userIndex = this.users.findIndex(user => user.id === id);
if (userIndex !== -1) {
this.users[userIndex] = { ...this.users[userIndex], ...updates };
}
}
// Action - 删除用户
deleteUser(id) {
this.users = this.users.filter(user => user.id !== id);
}
// Action - 设置当前用户
setCurrentUser(user) {
this.currentUser = user;
// 存储到localStorage
localStorage.setItem("currentUser", JSON.stringify(user));
}
// Computed - 筛选后的用户
get filteredUsers() {
let filtered = this.users;
if (this.filters.role !== "all") {
filtered = filtered.filter(user => user.role === this.filters.role);
}
if (this.filters.search) {
const searchTerm = this.filters.search.toLowerCase();
filtered = filtered.filter(
user =>
user.name.toLowerCase().includes(searchTerm) ||
user.email.toLowerCase().includes(searchTerm)
);
}
return filtered;
}
// Computed - 活跃用户数量
get activeUsersCount() {
return this.users.filter(user => user.status === "active").length;
}
}
// stores/rootStore.js
import { createContext, useContext } from "react";
import CounterStore from "./counterStore";
import UserStore from "./userStore";
import ProductStore from "./productStore";
import CartStore from "./cartStore";
class RootStore {
constructor() {
this.counterStore = new CounterStore();
this.userStore = new UserStore();
this.productStore = new ProductStore();
this.cartStore = new CartStore();
// 建立store之间的关联
this.setupCrossStoreActions();
}
setupCrossStoreActions() {
// 当用户登录时,加载购物车
reaction(
() => this.userStore.currentUser,
user => {
if (user) {
this.cartStore.loadCart(user.id);
} else {
this.cartStore.clearCart();
}
}
);
}
}
// 创建Context
const RootStoreContext = createContext(null);
// Provider组件
export const RootStoreProvider = ({ children }) => {
const store = new RootStore();
return <RootStoreContext.Provider value={store}>{children}</RootStoreContext.Provider>;
};
// Hook用于在组件中使用store
export const useStores = () => {
const store = useContext(RootStoreContext);
if (!store) {
throw new Error("useStores必须在RootStoreProvider内使用");
}
return store;
};
// 各个store的hooks
export const useCounterStore = () => useStores().counterStore;
export const useUserStore = () => useStores().userStore;
export const useProductStore = () => useStores().productStore;
export const useCartStore = () => useStores().cartStore;
// App.jsx
import { RootStoreProvider } from "./stores/rootStore";
function App() {
return (
<RootStoreProvider>
<div className="app">
<CounterDemo />
<UserManager />
<ProductList />
<ShoppingCart />
<ObserverDemo />
</div>
</RootStoreProvider>
);
}
// CounterDemo.jsx
import { observer } from "mobx-react-lite";
import { useCounterStore } from "./stores/rootStore";
const CounterDemo = observer(() => {
const counterStore = useCounterStore();
const [inputValue, setInputValue] = useState("");
return (
<div className="counter-demo">
<h2>MobX 计数器演示</h2>
<div className="counter-display">
<span>{counterStore.displayValue}</span>
<span>双倍值: {counterStore.double}</span>
</div>
<div className="controls">
<button onClick={() => counterStore.increment()}>增加</button>
<button onClick={() => counterStore.decrement()}>减少</button>
<div className="custom-increment">
<input
type="number"
value={inputValue}
onChange={e => setInputValue(e.target.value)}
placeholder="输入增加值"
/>
<button
onClick={() => {
counterStore.incrementByAmount(Number(inputValue) || 0);
setInputValue("");
}}
>
增加指定值
</button>
</div>
<button
onClick={() => counterStore.incrementAsync(5)}
disabled={counterStore.isLoading}
>
异步增加5
</button>
<button onClick={() => counterStore.undo()} disabled={!counterStore.canUndo}>
撤销
</button>
<button onClick={() => counterStore.reset()}>重置</button>
</div>
<div className="history">
<h3>操作历史</h3>
<ul>
{counterStore.history.map(record => (
<li key={record.id}>
{record.action} - {record.value} (
{new Date(record.timestamp).toLocaleTimeString()})
</li>
))}
</ul>
</div>
</div>
);
});
// UserManager.jsx
import { observer } from "mobx-react-lite";
import { useUserStore } from "./stores/rootStore";
const UserManager = observer(() => {
const userStore = useUserStore();
const [newUser, setNewUser] = useState({ name: "", email: "", role: "user" });
const [editingUser, setEditingUser] = useState(null);
useEffect(() => {
userStore.fetchUsers();
}, []);
const handleAddUser = () => {
userStore.addUser(newUser);
setNewUser({ name: "", email: "", role: "user" });
};
const handleUpdateUser = (id, updates) => {
userStore.updateUser(id, updates);
setEditingUser(null);
};
return (
<div className="user-manager">
<h2>用户管理 (MobX)</h2>
{/* 添加用户 */}
<div className="add-user-form">
<input
type="text"
placeholder="姓名"
value={newUser.name}
onChange={e => setNewUser({ ...newUser, name: e.target.value })}
/>
<input
type="email"
placeholder="邮箱"
value={newUser.email}
onChange={e => setNewUser({ ...newUser, email: e.target.value })}
/>
<select
value={newUser.role}
onChange={e => setNewUser({ ...newUser, role: e.target.value })}
>
<option value="user">普通用户</option>
<option value="admin">管理员</option>
</select>
<button onClick={handleAddUser}>添加用户</button>
</div>
{/* 筛选器 */}
<div className="filters">
<input
type="text"
placeholder="搜索用户..."
value={userStore.filters.search}
onChange={e => userStore.setFilter("search", e.target.value)}
/>
<select
value={userStore.filters.role}
onChange={e => userStore.setFilter("role", e.target.value)}
>
<option value="all">所有角色</option>
<option value="admin">管理员</option>
<option value="user">普通用户</option>
</select>
</div>
{/* 加载状态 */}
{userStore.loading && <div className="loading">加载中...</div>}
{userStore.error && <div className="error">{userStore.error}</div>}
{/* 统计信息 */}
<div className="stats">
<span>总用户数: {userStore.users.length}</span>
<span>活跃用户: {userStore.activeUsersCount}</span>
<span>筛选后: {userStore.filteredUsers.length}</span>
</div>
{/* 用户列表 */}
<div className="user-list">
{userStore.filteredUsers.map(user => (
<div key={user.id} className="user-card">
{editingUser === user.id ? (
<EditUserForm
user={user}
onSave={handleUpdateUser}
onCancel={() => setEditingUser(null)}
/>
) : (
<>
<div className="user-info">
<h4>{user.name}</h4>
<p>{user.email}</p>
<span className={`role ${user.role}`}>{user.role}</span>
{user.status && (
<span className={`status ${user.status}`}>
{user.status}
</span>
)}
</div>
<div className="user-actions">
<button onClick={() => setEditingUser(user.id)}>编辑</button>
<button
onClick={() => userStore.deleteUser(user.id)}
className="delete-btn"
>
删除
</button>
</div>
</>
)}
</div>
))}
</div>
{/* 分页 */}
<div className="pagination">
<button
onClick={() => userStore.setFilter("page", userStore.filters.page - 1)}
disabled={userStore.filters.page === 1}
>
上一页
</button>
<span>
第 {userStore.filters.page} 页 / 共 {userStore.pagination.pages} 页
</span>
<button
onClick={() => userStore.setFilter("page", userStore.filters.page + 1)}
disabled={userStore.filters.page >= userStore.pagination.pages}
>
下一页
</button>
</div>
</div>
);
});
// ProductList.jsx - 购物车集成
import { observer } from "mobx-react-lite";
import { useProductStore, useCartStore } from "./stores/rootStore";
const ProductList = observer(() => {
const productStore = useProductStore();
const cartStore = useCartStore();
useEffect(() => {
productStore.loadProducts();
}, []);
const handleAddToCart = product => {
cartStore.addItem(product);
};
return (
<div className="product-list">
<h2>商品列表</h2>
{productStore.loading && <div className="loading">加载商品...</div>}
{productStore.error && <div className="error">{productStore.error}</div>}
<div className="products">
{productStore.products.map(product => (
<div key={product.id} className="product-card">
<img src={product.image} alt={product.name} />
<div className="product-info">
<h4>{product.name}</h4>
<p>{product.description}</p>
<div className="price">¥{product.price}</div>
<div className="stock">库存: {product.stock}</div>
</div>
<div className="product-actions">
<button
onClick={() => handleAddToCart(product)}
disabled={product.stock === 0}
>
加入购物车
</button>
{cartStore.getItemQuantity(product.id) > 0 && (
<span className="in-cart">
已在购物车: {cartStore.getItemQuantity(product.id)}
</span>
)}
</div>
</div>
))}
</div>
</div>
);
});
// ShoppingCart.jsx
import { observer } from "mobx-react-lite";
import { useCartStore } from "./stores/rootStore";
const ShoppingCart = observer(() => {
const cartStore = useCartStore();
const [checkoutLoading, setCheckoutLoading] = useState(false);
const handleCheckout = async () => {
setCheckoutLoading(true);
try {
await cartStore.checkout();
alert("订单提交成功!");
} catch (error) {
alert("提交订单失败: " + error.message);
} finally {
setCheckoutLoading(false);
}
};
if (cartStore.items.length === 0) {
return (
<div className="shopping-cart empty">
<h2>购物车</h2>
<p>购物车是空的</p>
</div>
);
}
return (
<div className="shopping-cart">
<h2>购物车</h2>
<div className="cart-items">
{cartStore.items.map(item => (
<div key={item.id} className="cart-item">
<div className="item-info">
<h4>{item.name}</h4>
<p>单价: ¥{item.price}</p>
</div>
<div className="item-quantity">
<button
onClick={() => cartStore.updateQuantity(item.id, item.quantity - 1)}
disabled={item.quantity <= 1}
>
-
</button>
<span>{item.quantity}</span>
<button
onClick={() => cartStore.updateQuantity(item.id, item.quantity + 1)}
disabled={item.quantity >= item.maxStock}
>
+
</button>
</div>
<div className="item-total">¥{item.price * item.quantity}</div>
<button
onClick={() => cartStore.removeItem(item.id)}
className="remove-btn"
>
删除
</button>
</div>
))}
</div>
<div className="cart-summary">
<div className="summary-row">
<span>商品总数:</span>
<span>{cartStore.totalItems}</span>
</div>
<div className="summary-row">
<span>商品总价:</span>
<span>¥{cartStore.totalPrice}</span>
</div>
{cartStore.discount > 0 && (
<div className="summary-row discount">
<span>优惠:</span>
<span>-¥{cartStore.discount}</span>
</div>
)}
<div className="summary-row total">
<span>应付金额:</span>
<span>¥{cartStore.finalPrice}</span>
</div>
</div>
<div className="cart-actions">
<button onClick={() => cartStore.clearCart()} className="clear-btn">
清空购物车
</button>
<button
onClick={handleCheckout}
disabled={checkoutLoading || cartStore.items.length === 0}
className="checkout-btn"
>
{checkoutLoading ? "处理中..." : "结算"}
</button>
</div>
{/* 折扣信息 */}
{cartStore.availableCoupons.length > 0 && (
<div className="coupons">
<h4>可用优惠券</h4>
{cartStore.availableCoupons.map(coupon => (
<div key={coupon.code} className="coupon">
<span>
{coupon.code} - {coupon.description}
</span>
<button onClick={() => cartStore.applyCoupon(coupon.code)}>使用</button>
</div>
))}
</div>
)}
</div>
);
});
// ObserverDemo.jsx - 性能优化
import { observer, Observer, useLocalObservable } from "mobx-react-lite";
// 1. 使用 observer HOC
const ObservedComponent = observer(({ data }) => {
return <div>{data.value}</div>;
});
// 2. 使用 Observer 组件(局部观察)
const LocalObserverDemo = ({ items }) => {
return (
<div>
{items.map(item => (
<Observer key={item.id}>
{() => (
<div>
{item.name} - {item.price}
</div>
)}
</Observer>
))}
</div>
);
};
// 3. 使用 useLocalObservable(组件内部状态)
const LocalStoreDemo = () => {
const localStore = useLocalObservable(() => ({
count: 0,
increment() {
this.count++;
},
get double() {
return this.count * 2;
},
}));
return (
<div>
<span>计数: {localStore.count}</span>
<span>双倍: {localStore.double}</span>
<button onClick={localStore.increment}>增加</button>
</div>
);
};
// 4. 性能优化 - 避免不必要的重新渲染
const OptimizedComponent = observer(({ userStore }) => {
// 只观察需要的部分
const { currentUser, loading } = userStore;
// 计算值放在组件外部
const userDisplayName = computed(() => {
if (!currentUser) return "未登录";
return `${currentUser.name} (${currentUser.role})`;
}).get();
// 使用 useMemo 避免重复计算
const formattedDate = useMemo(() => {
return new Date().toLocaleDateString();
}, []);
return (
<div>
<h3>当前用户: {userDisplayName}</h3>
<p>日期: {formattedDate}</p>
{loading && <div>加载中...</div>}
</div>
);
});
// stores/advancedStore.js
import {
observable,
action,
computed,
reaction,
autorun,
when,
makeObservable,
configure,
runInAction,
} from "mobx";
// 配置MobX
configure({
enforceActions: "always", // 强制使用action修改状态
computedRequiresReaction: true, // 计算值需要在reaction中访问
reactionRequiresObservable: true, // reaction需要观察observable
observableRequiresReaction: false,
disableErrorBoundaries: false, // 是否禁用错误边界
});
class AdvancedStore {
constructor() {
// 显式定义observable和action
makeObservable(this, {
items: observable,
selectedItem: observable,
loading: observable,
error: observable,
addItem: action,
removeItem: action,
selectItem: action,
clearItems: action,
loadItems: action,
totalCount: computed,
selectedItemDetails: computed,
hasItems: computed,
});
this.items = [];
this.selectedItem = null;
this.loading = false;
this.error = null;
this.setupAdvancedReactions();
}
// 异步action
async loadItems() {
this.loading = true;
this.error = null;
try {
const response = await fetch("/api/items");
const data = await response.json();
// 在action中更新状态
runInAction(() => {
this.items = data;
this.loading = false;
});
} catch (error) {
runInAction(() => {
this.error = error.message;
this.loading = false;
});
}
}
// 批量action
batchAddItems(newItems) {
// 使用transaction或runInAction进行批量更新
runInAction(() => {
newItems.forEach(item => {
this.items.push(item);
});
});
}
// 计算值
get totalCount() {
return this.items.length;
}
get selectedItemDetails() {
if (!this.selectedItem) return null;
return {
...this.selectedItem,
formattedDate: new Date(this.selectedItem.createdAt).toLocaleDateString(),
};
}
get hasItems() {
return this.totalCount > 0;
}
// 高级reaction设置
setupAdvancedReactions() {
// 1. autorun - 自动运行
this.autoSaveDisposer = autorun(() => {
if (this.items.length > 0) {
// 自动保存到localStorage
localStorage.setItem("items_backup", JSON.stringify(this.items));
}
});
// 2. reaction - 响应特定变化
this.selectionReactionDisposer = reaction(
() => this.selectedItem,
selectedItem => {
if (selectedItem) {
console.log("选中了项目:", selectedItem.name);
// 可以触发其他副作用
}
}
);
// 3. when - 条件满足时执行一次
when(
() => this.items.length >= 10,
() => {
console.log("项目数量达到10个!");
}
);
}
// 清理reaction
cleanup() {
this.autoSaveDisposer?.();
this.selectionReactionDisposer?.();
}
}
// 衍生状态管理
class DerivedStateStore {
constructor() {
makeObservable(this, {
rawData: observable,
processedData: computed,
statistics: computed,
filter: observable,
filteredData: computed,
});
this.rawData = [];
this.filter = {
minValue: 0,
maxValue: 100,
category: "all",
};
}
// 异步加载数据
async fetchData() {
const response = await fetch("/api/data");
const data = await response.json();
runInAction(() => {
this.rawData = data;
});
}
// 计算处理后的数据
get processedData() {
return this.rawData.map(item => ({
...item,
normalizedValue: item.value / 100,
timestamp: new Date(item.timestamp),
}));
}
// 计算统计信息
get statistics() {
const values = this.processedData.map(item => item.value);
return {
count: values.length,
sum: values.reduce((a, b) => a + b, 0),
avg: values.length > 0 ? values.reduce((a, b) => a + b, 0) / values.length : 0,
min: Math.min(...values),
max: Math.max(...values),
};
}
// 计算筛选后的数据
get filteredData() {
return this.processedData.filter(item => {
const matchesValue =
item.value >= this.filter.minValue && item.value <= this.filter.maxValue;
const matchesCategory =
this.filter.category === "all" || item.category === this.filter.category;
return matchesValue && matchesCategory;
});
}
}
// 时间旅行(撤销/重做)
class TimeTravelStore {
constructor() {
makeObservable(this, {
currentState: observable,
past: observable,
future: observable,
updateState: action,
undo: action,
redo: action,
canUndo: computed,
canRedo: computed,
});
this.currentState = { value: 0 };
this.past = [];
this.future = [];
this.maxHistory = 50;
}
updateState(newState) {
// 保存当前状态到历史
this.past.push({ ...this.currentState, timestamp: Date.now() });
// 限制历史记录长度
if (this.past.length > this.maxHistory) {
this.past.shift();
}
// 更新当前状态
this.currentState = newState;
// 清除重做历史
this.future = [];
}
undo() {
if (this.past.length === 0) return;
// 当前状态保存到重做历史
this.future.push(this.currentState);
// 恢复到上一个状态
this.currentState = this.past.pop();
}
redo() {
if (this.future.length === 0) return;
// 当前状态保存到历史
this.past.push(this.currentState);
// 恢复到重做状态
this.currentState = this.future.pop();
}
get canUndo() {
return this.past.length > 0;
}
get canRedo() {
return this.future.length > 0;
}
// 获取历史快照
getHistorySnapshot() {
return {
past: [...this.past],
present: this.currentState,
future: [...this.future],
};
}
}
| 特性 | Redux (with Toolkit) | MobX |
|---|---|---|
| 哲学 | 函数式编程,不可变数据 | 面向对象,可变数据 |
| 学习曲线 | 中等,概念较多 | 简单,更接近普通 JS |
| 样板代码 | 较少(现代 Redux) | 很少 |
| 性能 | 需要手动优化 | 自动优化,细粒度响应 |
| 调试 | 优秀的时间旅行 | 良好,但不如 Redux |
| 异步处理 | RTK Query/Thunk | flow/async actions |
| 社区生态 | 非常丰富 | 良好 |
| TypeScript 支持 | 优秀 | 优秀 |
选择 Redux 当:
选择 MobX 当:
// 在大型项目中可以混合使用
// 1. 全局状态用 Redux
// 2. 局部状态用 MobX
// 3. 表单状态用 Formik/Final Form
// 或者在 Redux 中集成 MobX 理念
// 使用 Redux 但采用更灵活的局部状态管理
import { create } from "zustand";
import { persist, devtools, subscribeWithSelector } from "zustand/middleware";
const useStore = create(
devtools(
persist(
subscribeWithSelector((set, get) => ({
count: 0,
users: [],
increment: () => set(state => ({ count: state.count + 1 })),
addUser: user =>
set(state => ({
users: [...state.users, user],
})),
// 异步操作
fetchUsers: async () => {
const response = await fetch("/api/users");
const users = await response.json();
set({ users });
},
})),
{
name: "app-storage", // localStorage key
getStorage: () => localStorage,
}
)
)
);
import { atom, selector, useRecoilState, useRecoilValue } from "recoil";
const countState = atom({
key: "countState",
default: 0,
});
const doubleCountState = selector({
key: "doubleCountState",
get: ({ get }) => {
const count = get(countState);
return count * 2;
},
});
function Counter() {
const [count, setCount] = useRecoilState(countState);
const doubleCount = useRecoilValue(doubleCountState);
return (
<div>
Count: {count}
Double: {doubleCount}
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
React 状态管理有多种选择,关键是根据项目需求选择合适方案: