react 之redux

一 . 官方使用流程

Redux 是一个用于管理 React 应用"状态"的官方工具,它充当了一个独立的"状态仓库"类似于Vue当中的pinia,让组件可以以一种可预测的方式来获取和更新状态,尤其解决了多个组件需要共享同一状态时的复杂通信问题。

二 . 三大原则

  1. 单一数据源

    • 核心 :整个应用的状态,存储在一个唯一的 Store 中。
    • 好处:状态集中,容易调试和追踪。
  2. 状态只读

    • 核心 :你不能直接修改 State。改变状态的唯一方法是 dispatch 一个 action
    • 好处:所有状态变化都变得可预测、可追溯。
  3. 使用纯函数进行更改

    • 核心 :指定状态如何更新的 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 项目
相关推荐
文心快码BaiduComate12 小时前
基于YOLOv8的动漫人脸角色识别系统:Comate完成前端开发
前端·后端·前端框架
ら陈佚晨12 小时前
React 18 的核心设计理念:并发渲染
前端·javascript·react.js·前端框架·fiber
草字13 小时前
uniapp 打开横竖屏。usb调试时可以横竖屏切换,但是打包发布后却不行?
java·前端·uni-app
EF@蛐蛐堂13 小时前
Federation vs Garfish vs Micro-app 微前端选型(二)
前端·vue.js·前端框架
洋不写bug13 小时前
前端html基础标签
前端·html
GISer_Jing13 小时前
前端学习总结——AI&主流前沿方向篇
前端·人工智能·学习
尘世中一位迷途小书童13 小时前
Monorepo 工具大比拼:为什么我最终选择了 pnpm + Turborepo?
前端·架构
一枚前端小能手13 小时前
🔍 重写vue之ref和reactive
前端·javascript·vue.js
星链引擎13 小时前
4sapi.com开发者进阶版(技术导向,侧重 “原理 + 最佳实践”)
前端