当前位置: 首页 > article >正文

HOW - React 状态模块化管理和按需加载(一) - react-redux

目录

  • 一、背景
  • 二、react-redux
    • 模块化管理
      • 1. 模块化文件结构
      • 2. 使用 Redux Toolkit 的 Slice
        • 例子:用户模块 (userSlice)
        • 例子:商品模块 (productSlice)
      • 3. 合并 Reducers
      • 4. 配置 Store
      • 5. 使用 Redux 状态和操作
        • 例子:获取用户信息
      • 6. 拓展
    • 按需加载
      • 1. 动态模块加载的核心逻辑
      • 2. 实现动态加载 Reducer
        • 修改 Store 配置
      • 3. 动态注入模块的 Reducer
        • 示例:动态加载 `user` 模块
        • 示例:动态加载 `product` 模块
      • 4. 组件中按需加载模块
        • 示例:加载 `user` 模块
      • 5. 结合代码分割
        • 示例:使用 React 的 `lazy` 和 `Suspense`
      • 6. 清除不需要的模块
        • 实现移除 Reducer 的方法
        • 示例:移除 `user` 模块
      • 7. 优势

一、背景

在构建 React 项目时,必不可少的就是一些全局或者多组件共享数据的管理。官方推荐使用 react-redux.

但现在还流行另外一个框架,jotai.

我们今天就来学习一下如何基于 react-redux进行状态模块化管理和按需加载,包括为什么且在什么场景下可以引入 jotai 使用。

二、react-redux

关于 react-redux 基本使用就不过多介绍了。

模块化管理

在 React + Redux 项目中,将 Redux 状态和逻辑分模块管理是一种常见的实践,可以提高代码的可维护性和可扩展性。以下是分模块管理 Redux 的常用方法和步骤:

1. 模块化文件结构

一种常见的项目结构是将每个功能模块的状态管理放在单独的文件夹中。
例如,一个项目包含用户管理和商品管理模块,可以按如下结构组织:

src/
├── store/          // Redux 全局 store
│   ├── index.ts    // 配置 store
│   ├── rootReducer.ts // 合并 reducers
├── features/
│   ├── user/       // 用户模块
│   │   ├── userSlice.ts
│   │   ├── userActions.ts
│   │   ├── userSelectors.ts
│   ├── product/    // 商品模块
│   │   ├── productSlice.ts
│   │   ├── productActions.ts
│   │   ├── productSelectors.ts

2. 使用 Redux Toolkit 的 Slice

Redux Toolkit 提供了 createSlice 方法,可以更轻松地管理每个模块的状态和操作。

例子:用户模块 (userSlice)
// src/features/user/userSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

interface UserState {
  userInfo: { id: string; name: string } | null;
  isLoading: boolean;
}

const initialState: UserState = {
  userInfo: null,
  isLoading: false,
};

const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    setUserInfo(state, action: PayloadAction<{ id: string; name: string }>) {
      state.userInfo = action.payload;
    },
    clearUserInfo(state) {
      state.userInfo = null;
    },
    setLoading(state, action: PayloadAction<boolean>) {
      state.isLoading = action.payload;
    },
  },
});

export const { setUserInfo, clearUserInfo, setLoading } = userSlice.actions;
export default userSlice.reducer;
例子:商品模块 (productSlice)
// src/features/product/productSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

interface Product {
  id: string;
  name: string;
  price: number;
}

interface ProductState {
  productList: Product[];
  isLoading: boolean;
}

const initialState: ProductState = {
  productList: [],
  isLoading: false,
};

const productSlice = createSlice({
  name: 'product',
  initialState,
  reducers: {
    setProducts(state, action: PayloadAction<Product[]>) {
      state.productList = action.payload;
    },
    addProduct(state, action: PayloadAction<Product>) {
      state.productList.push(action.payload);
    },
    setLoading(state, action: PayloadAction<boolean>) {
      state.isLoading = action.payload;
    },
  },
});

export const { setProducts, addProduct, setLoading } = productSlice.actions;
export default productSlice.reducer;

3. 合并 Reducers

使用 Redux Toolkit 的 combineReducers 将模块化的 reducer 合并。

// src/store/rootReducer.ts
import { combineReducers } from '@reduxjs/toolkit';
import userReducer from '../features/user/userSlice';
import productReducer from '../features/product/productSlice';

const rootReducer = combineReducers({
  user: userReducer,
  product: productReducer,
});

export default rootReducer;

4. 配置 Store

在 Redux Toolkit 中,通过 configureStore 来配置全局 Store。

// src/store/index.ts
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './rootReducer';

const store = configureStore({
  reducer: rootReducer,
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

export default store;

5. 使用 Redux 状态和操作

在组件中通过 useSelectoruseDispatch 使用 Redux 状态和操作。

例子:获取用户信息
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState } from '../../store';
import { setUserInfo } from '../../features/user/userSlice';

const UserProfile: React.FC = () => {
  const userInfo = useSelector((state: RootState) => state.user.userInfo);
  const dispatch = useDispatch();

  const handleLogin = () => {
    dispatch(setUserInfo({ id: '1', name: 'John Doe' }));
  };

  return (
    <div>
      {userInfo ? (
        <p>Welcome, {userInfo.name}</p>
      ) : (
        <p>Please log in</p>
      )}
      <button onClick={handleLogin}>Log In</button>
    </div>
  );
};

export default UserProfile;

6. 拓展

  • 异步操作:使用 Redux Toolkit 的 createAsyncThunk 管理异步逻辑。
  • 代码分割:结合动态加载模块,按需加载 Redux 状态和逻辑。
  • 测试:为每个模块单独编写单元测试,提高代码稳定性。

这种分模块的方式可以帮助你清晰地组织 Redux 状态,增强项目的可维护性。

按需加载

在某些场景下,只需要多个模块之间共享一份数据,即这份数据并不是 global 全局的。

则可以通过 按需加载 Redux 模块 来实现内存中只加载需要的模块,从而优化性能和内存占用。

这通常结合 动态模块加载 和 Redux 的 replaceReducer 功能来实现。

以下是如何实现按需加载 Redux 模块的完整方案:

1. 动态模块加载的核心逻辑

Redux 支持使用 store.replaceReducer 替换当前的根 reducer,这样就可以在需要时动态添加新的模块。

2. 实现动态加载 Reducer

修改 Store 配置

首先,创建一个支持动态加载的 Store。

// src/store/index.ts
import { configureStore, Reducer, CombinedState } from '@reduxjs/toolkit';
import { combineReducers } from 'redux';

type AsyncReducers = {
  [key: string]: Reducer;
};

const createRootReducer = (asyncReducers: AsyncReducers) =>
  combineReducers({
    // 这里可以放一些全局的静态 reducer
    ...asyncReducers,
  });

const store = configureStore({
  reducer: createRootReducer({}),
});

type RootState = ReturnType<typeof store.getState>;
type AppDispatch = typeof store.dispatch;

store.asyncReducers = {} as AsyncReducers;

store.injectReducer = (key: string, reducer: Reducer) => {
  if (!store.asyncReducers[key]) {
    store.asyncReducers[key] = reducer;
    store.replaceReducer(createRootReducer(store.asyncReducers));
  }
};

export { RootState, AppDispatch };
export default store;

说明:

  • store.asyncReducers 用于存储动态加载的 reducers。
  • store.injectReducer 是动态注入 reducer 的方法。

3. 动态注入模块的 Reducer

在需要时加载模块并动态注入其 reducer。

示例:动态加载 user 模块
// src/features/user/index.ts
import userReducer from './userSlice';
import store from '../../store';

store.injectReducer('user', userReducer);
示例:动态加载 product 模块
// src/features/product/index.ts
import productReducer from './productSlice';
import store from '../../store';

store.injectReducer('product', productReducer);

4. 组件中按需加载模块

在组件中使用模块时,可以按需加载 reducer。

示例:加载 user 模块
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState } from '../../store';
import { setUserInfo } from './userSlice';

// 动态加载 user 模块
import('./index');

const UserProfile: React.FC = () => {
  const userInfo = useSelector((state: RootState) => state.user?.userInfo);
  const dispatch = useDispatch();

  useEffect(() => {
    if (!userInfo) {
      dispatch(setUserInfo({ id: '1', name: 'John Doe' }));
    }
  }, [dispatch, userInfo]);

  return <div>Welcome, {userInfo?.name || 'Guest'}</div>;
};

export default UserProfile;

说明

  • import('./index') 动态加载 user 模块并注入 reducer。
  • 使用时无需提前加载其他模块,减少内存占用。

5. 结合代码分割

使用 Webpack 或 Vite 的代码分割功能,将每个模块单独打包,按需加载。

示例:使用 React 的 lazySuspense
import React, { Suspense } from 'react';

const UserProfile = React.lazy(() => import('./features/user/UserProfile'));

const App: React.FC = () => {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <UserProfile />
    </Suspense>
  );
};

export default App;

效果

  • UserProfile 页面访问时,动态加载 user 模块。
  • 内存中仅保留使用过的模块(如 user),未使用的模块(如 product)不会加载。

6. 清除不需要的模块

如果模块加载后不再需要,可以从内存中移除 reducer。

实现移除 Reducer 的方法
store.removeReducer = (key: string) => {
  if (store.asyncReducers[key]) {
    delete store.asyncReducers[key];
    store.replaceReducer(createRootReducer(store.asyncReducers));
  }
};
示例:移除 user 模块
store.removeReducer('user');

7. 优势

  • 减少内存占用:未使用的模块不会加载到内存中。
  • 优化首屏性能:只加载当前页面需要的模块。
  • 代码分离:结合动态加载和代码分割,实现更清晰的模块管理。

这种按需加载 Redux 模块的方式非常适合大型项目,尤其是模块众多但同时使用的模块有限的场景。


http://www.kler.cn/a/411313.html

相关文章:

  • 计算机操作系统——进程控制(Linux)
  • 嵌入式硬件设计:从概念到实现的全流程
  • 前端---CSS(部分用法)
  • Linux 中 find 命令使用详解
  • 了解M有SQL索引
  • .net 支持跨平台(桌面)系列技术汇总
  • 【Python中while循环】
  • Spring Boot 整合 ELK 全面指南:实现日志采集、分析与可视化
  • java——利用 Tomcat 自定义的类加载器实现热加载
  • 最长回文子串&多/虚继承
  • 网络安全原理与技术思考题/简答题
  • 1126刷题
  • 堆的实现(完全注释版本)
  • pikachu文件上传漏洞通关详解
  • 利用 Vue 组合式 API 与 requestAnimationFrame 优化大量元素渲染
  • Paddle Inference部署推理(一)
  • QT-installEventFilter
  • GaussDB高智能--库内AI引擎:模型管理数据集管理
  • 蓝桥杯c++算法秒杀【6】之动态规划【下】(数字三角形、砝码称重(背包问题)、括号序列、异或三角:::非常典型的必刷例题!!!)
  • 前端 Vue 3 后端 Node.js 和Express 结合cursor常见提示词结构
  • C语言解析命令行参数
  • xiaolin coding 图解网络笔记——TCP篇
  • 2686694 - 操作方法:MSEG - DBSQL_REDIRECT_INCONSISTENCY
  • 道路机器人识别交通灯,马路,左右转,黄线,人行道,机器人等路面导航标志识别-使用YOLO标记
  • Python毕业设计选题:基于django+vue的期货交易模拟系统的设计与实现
  • PyTorch3