一 . 官方使用流程
Redux 是一个用于管理 React 应用"状态"的官方工具,它充当了一个独立的"状态仓库"类似于Vue当中的pinia,让组件可以以一种可预测的方式来获取和更新状态,尤其解决了多个组件需要共享同一状态时的复杂通信问题。
二 . 三大原则
-
单一数据源
- 核心 :整个应用的状态,存储在一个唯一的 Store 中。
- 好处:状态集中,容易调试和追踪。
-
状态只读
- 核心 :你不能直接修改 State。改变状态的唯一方法是
dispatch
一个action
。 - 好处:所有状态变化都变得可预测、可追溯。
- 核心 :你不能直接修改 State。改变状态的唯一方法是
-
使用纯函数进行更改
- 核心 :指定状态如何更新的
reducer
必须是一个纯函数(同样的输入,必定得到同样的输出)。 - 好处:状态更新逻辑清晰、稳定,没有副作用。
- 核心 :指定状态如何更新的
官方解释:redux.xiniushu.com/understandi...
三 . 使用方式
js
src/
├── store/
│ ├── actions/
│ │ └── userActions.js
│ ├── reducers/
│ │ ├── userReducer.js
│ │ └── index.js
│ └── index.js
├── components/
│ ├── Login.js
│ └── UserProfile.js
└── App.js
步骤 1:定义 Action Types
src/store/actions/userActions.js
js
//userActions.js
// 定义 Action 类型常量
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
export const LOGOUT = 'LOGOUT';
export const UPDATE_USER_INFO = 'UPDATE_USER_INFO';
// Action Creators
export const loginSuccess = (userData) => ({
type: LOGIN_SUCCESS,
payload: userData
});
export const logout = () => ({
type: LOGOUT
});
export const updateUserInfo = (userInfo) => ({
type: UPDATE_USER_INFO,
payload: userInfo
});
步骤 2:创建 Reducer
src/store/reducers/userReducer.js
js
//userReducer.js
import {
LOGIN_SUCCESS,
LOGOUT,
UPDATE_USER_INFO
} from '../actions/userActions';
// 初始状态
const initialState = {
isAuthenticated: false,
user: null,
token: null,
lastLogin: null
};
// Reducer 函数
const userReducer = (state = initialState, action) => {
switch (action.type) {
case LOGIN_SUCCESS:
return {
...state,
isAuthenticated: true,
user: action.payload.user,
token: action.payload.token,
lastLogin: new Date().toISOString()
};
case LOGOUT:
return {
...initialState
};
case UPDATE_USER_INFO:
return {
...state,
user: {
...state.user,
...action.payload
}
};
default:
return state;
}
};
export default userReducer;
步骤 3:组合 Reducers
src/store/reducers/index.js
js
//src/store/reducers/index.js
import { combineReducers } from 'redux';
import userReducer from './userReducer';
// 如果有多个 reducer,可以在这里组合
const rootReducer = combineReducers({
user: userReducer,
// 其他 reducer 可以在这里添加
// posts: postsReducer,
// comments: commentsReducer
});
export default rootReducer;
步骤 4:创建 Store
src/store/index.js
js
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from './reducers';
// 初始状态(可选)
const initialState = {};
// 中间件(可选,比如可以添加 redux-thunk 处理异步操作)
const middleware = [];
// 创建 Store
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
步骤 5:Provider 包裹应用
src/index.js
js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
步骤 6:创建组件并使用 Redux
src/components/Login.js
js
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { loginSuccess, logout } from '../store/actions/userActions';
const Login = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const dispatch = useDispatch();
const { isAuthenticated, user } = useSelector(state => state.user);
const handleLogin = (e) => {
e.preventDefault();
// 模拟登录 API 调用
const mockUserData = {
user: {
id: 1,
username: username,
email: `${username}@example.com`,
avatar: `https://i.pravatar.cc/150?u=${username}`
},
token: 'mock-jwt-token-here'
};
dispatch(loginSuccess(mockUserData));
setUsername('');
setPassword('');
};
const handleLogout = () => {
dispatch(logout());
};
if (isAuthenticated) {
return (
<div style={{ padding: '20px', border: '1px solid #ccc', margin: '10px' }}>
<h3>已登录</h3>
<p>欢迎, {user?.username}!</p>
<button onClick={handleLogout}>退出登录</button>
</div>
);
}
return (
<div style={{ padding: '20px', border: '1px solid #ccc', margin: '10px' }}>
<h3>登录</h3>
<form onSubmit={handleLogin}>
<div>
<input
type="text"
placeholder="用户名"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
</div>
<div>
<input
type="password"
placeholder="密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button type="submit">登录</button>
</form>
</div>
);
};
export default Login;
src/components/UserProfile.js
js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { updateUserInfo } from '../store/actions/userActions';
const UserProfile = () => {
const { user, isAuthenticated, lastLogin } = useSelector(state => state.user);
const dispatch = useDispatch();
const handleUpdateProfile = () => {
const newInfo = {
bio: `这是 ${user.username} 的新简介`,
updatedAt: new Date().toISOString()
};
dispatch(updateUserInfo(newInfo));
};
if (!isAuthenticated) {
return (
<div style={{ padding: '20px', border: '1px solid #ccc', margin: '10px' }}>
<p>请先登录</p>
</div>
);
}
return (
<div style={{ padding: '20px', border: '1px solid #ccc', margin: '10px' }}>
<h3>用户资料</h3>
<img
src={user.avatar}
alt="头像"
style={{ width: '100px', height: '100px', borderRadius: '50%' }}
/>
<p><strong>用户名:</strong> {user.username}</p>
<p><strong>邮箱:</strong> {user.email}</p>
<p><strong>最后登录:</strong> {new Date(lastLogin).toLocaleString()}</p>
{user.bio && <p><strong>简介:</strong> {user.bio}</p>}
<button onClick={handleUpdateProfile} style={{ marginTop: '10px' }}>
更新简介
</button>
</div>
);
};
export default UserProfile;
步骤 7:主应用组件
src/App.js
js
import React from 'react';
import { useSelector } from 'react-redux';
import Login from './components/Login';
import UserProfile from './components/UserProfile';
const App = () => {
const { isAuthenticated, user } = useSelector(state => state.user);
return (
<div style={{ maxWidth: '800px', margin: '0 auto', padding: '20px' }}>
<h1>Redux 用户认证示例</h1>
<div style={{
backgroundColor: isAuthenticated ? '#d4edda' : '#f8d7da',
padding: '10px',
marginBottom: '20px',
borderRadius: '5px'
}}>
<strong>当前状态: </strong>
{isAuthenticated ? `已登录 (用户: ${user?.username})` : '未登录'}
</div>
<Login />
<UserProfile />
{/* Redux 状态查看器(用于调试) */}
<details style={{ marginTop: '20px' }}>
<summary>Redux Store 状态查看</summary>
<pre style={{
backgroundColor: '#f5f5f5',
padding: '10px',
borderRadius: '5px',
overflow: 'auto'
}}>
{JSON.stringify(useSelector(state => state), null, 2)}
</pre>
</details>
</div>
);
};
export default App;
以上为了展示 Redux 的核心概念(action、reducer、store)用的是传统写法,现在都是使用Redux Toolkit
✅ 使用 Redux Toolkit 的现代写法
步骤 1:安装
bash
bash
npm install @reduxjs/toolkit react-redux
步骤 2:创建 Store 和 Slice
src/store/index.js
js
import { configureStore } from '@reduxjs/toolkit';
import userSlice from './userSlice';
// 一行代码搞定 store 配置(自动包含 Redux DevTools、thunk 中间件等)
export const store = configureStore({
reducer: {
user: userSlice,
},
});
export default store;
src/store/userSlice.js
js
import { createSlice } from '@reduxjs/toolkit';
// 使用 createSlice 自动生成 actions 和 reducer
const userSlice = createSlice({
name: 'user',
initialState: {
isAuthenticated: false,
user: null,
token: null,
lastLogin: null
},
reducers: {
// 可以直接"突变"状态,Immer 在内部处理不可变更新
loginSuccess: (state, action) => {
state.isAuthenticated = true;
state.user = action.payload.user;
state.token = action.payload.token;
state.lastLogin = new Date().toISOString();
},
logout: (state) => {
state.isAuthenticated = false;
state.user = null;
state.token = null;
state.lastLogin = null;
},
updateUserInfo: (state, action) => {
if (state.user) {
// 直接修改嵌套属性
state.user = { ...state.user, ...action.payload };
}
}
}
});
// 自动生成 action creators
export const { loginSuccess, logout, updateUserInfo } = userSlice.actions;
// 自动生成 reducer
export default userSlice.reducer;
步骤 3:在组件中使用(与之前相同)
src/components/Login.js
js
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { loginSuccess, logout } from '../store/userSlice'; // 直接从 slice 导入
const Login = () => {
const [username, setUsername] = useState('');
const dispatch = useDispatch();
const { isAuthenticated, user } = useSelector(state => state.user);
const handleLogin = (e) => {
e.preventDefault();
const mockUserData = {
user: {
id: 1,
username: username,
email: `${username}@example.com`,
},
token: 'mock-jwt-token'
};
dispatch(loginSuccess(mockUserData)); // 直接使用生成的 action creator
setUsername('');
};
// ... 其余代码相同
};
export default Login;
在这里你会发现Redux Toolkit 和 Pinia 在设计理念和开发者体验上确实非常相似,它们都代表了现代状态管理库的发展方向。
🎯 核心相似之处
1. API 设计哲学
js
// ✅ Redux Toolkit
import { createSlice, configureStore } from '@reduxjs/toolkit';
const userSlice = createSlice({
name: 'user',
initialState: { name: '', isLoggedIn: false },
reducers: {
login(state, action) {
state.isLoggedIn = true; // 直接"突变"
state.name = action.payload;
}
}
});
// ✅ Pinia
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({ name: '', isLoggedIn: false }),
actions: {
login(name) {
this.isLoggedIn = true; // 直接"突变"
this.name = name;
}
}
});
2. 直接的状态"突变"
两者都放弃了严格的不可变更新,改用代理实现:
js
// ❌ 传统 Redux - 手动不可变
return {
...state,
user: {
...state.user,
name: action.payload
}
};
// ✅ RTK & Pinia - 直接赋值
state.user.name = action.payload; // RTK
this.user.name = payload; // Pinia
3. Store 创建方式
js
// ✅ RTK
import { configureStore } from '@reduxjs/toolkit';
export const store = configureStore({
reducer: { user: userSlice }
});
// ✅ Pinia
import { createPinia } from 'pinia';
export const pinia = createPinia();
🔄 设计理念的趋同
共同目标:降低复杂度,提升开发者体验
传统痛点 | RTK 解决方案 | Pinia 解决方案 |
---|---|---|
模板代码多 | createSlice 自动生成 |
defineStore 简洁定义 |
不可变更新复杂 | Immer 代理 | Proxy 代理 |
配置繁琐 | configureStore 默认配置 |
createPinia 零配置 |
模块化复杂 | 多个 slice 组合 | 多个 store 独立 |
📊 主要区别
1. 核心哲学差异
js
// 🔴 Redux Toolkit - 保持 Redux 核心概念
- 单一 Store
- Actions 和 Reducers 分离
- 不可变数据流(底层)
- 中间件生态系统
// 🟢 Pinia - Vue 化设计
- 多个 Store
- Actions 和 Mutations 合并
- 响应式数据(基于 Vue)
- 更好的 TypeScript 支持
2. 架构差异
js
// RTK - 中心化 Store
const store = configureStore({
reducer: {
user: userReducer, // 模块作为 reducer
posts: postsReducer, // 组合到单一 store
cart: cartReducer
}
});
// Pinia - 分布式 Stores
const userStore = useUserStore(); // 独立 store
const postsStore = usePostsStore(); // 按需使用
const cartStore = useCartStore(); // 不需要组合
3. 异步处理
js
// ✅ RTK - createAsyncThunk
const fetchUser = createAsyncThunk(
'user/fetch',
async (userId) => {
const response = await userAPI.fetchById(userId);
return response.data;
}
);
// ✅ Pinia - 直接写 async
actions: {
async fetchUser(userId) {
const response = await userAPI.fetchById(userId);
this.user = response.data;
}
}
4. 各自的定位
js
// RTK 的思考:"如何让 Redux 更好用,但不放弃核心原则?"
- 保持可预测性
- 保持中间件生态
- 渐进式改进
// Pinia 的思考:"如何为 Vue 设计最自然的状态管理?"
- 拥抱 Vue 响应式
- 更好的组合式 API 集成
- 更简单的学习曲线
🚀 实际选择建议
选择 RTK 当:
js
// 1. React 技术栈
import { useSelector, useDispatch } from 'react-redux';
// 2. 需要强大的中间件生态
import { createLogger } from 'redux-logger';
import { reduxBatch } from '@manaflair/redux-batch';
// 3. 大型团队,需要严格的数据流约束
// 4. 从传统 Redux 迁移
选择 Pinia 当:
js
// 1. Vue 技术栈
import { useUserStore } from '@/stores/user';
// 2. 想要更简单的学习曲线
// 3. 更好的 TypeScript 体验
// 4. 组合式 API 项目