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

目录

  • 一、背景
  • 二、react-redux
    • 模块化管理
    • 按需加载
      • [1. 动态模块加载的核心逻辑](#1. 动态模块加载的核心逻辑)
      • [2. 实现动态加载 Reducer](#2. 实现动态加载 Reducer)
        • [修改 Store 配置](#修改 Store 配置)
      • [3. 动态注入模块的 Reducer](#3. 动态注入模块的 Reducer)
        • [示例:动态加载 `user` 模块](#示例:动态加载 user 模块)
        • [示例:动态加载 `product` 模块](#示例:动态加载 product 模块)
      • [4. 组件中按需加载模块](#4. 组件中按需加载模块)
        • [示例:加载 `user` 模块](#示例:加载 user 模块)
      • [5. 结合代码分割](#5. 结合代码分割)
        • [示例:使用 React 的 `lazy` 和 `Suspense`](#示例:使用 React 的 lazySuspense)
      • [6. 清除不需要的模块](#6. 清除不需要的模块)
        • [实现移除 Reducer 的方法](#实现移除 Reducer 的方法)
        • [示例:移除 `user` 模块](#示例:移除 user 模块)
      • [7. 优势](#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)
typescript 复制代码
// 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)
typescript 复制代码
// 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 合并。

typescript 复制代码
// 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。

typescript 复制代码
// 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 状态和操作。

例子:获取用户信息
typescript 复制代码
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。

typescript 复制代码
// 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 模块
typescript 复制代码
// src/features/user/index.ts
import userReducer from './userSlice';
import store from '../../store';

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

store.injectReducer('product', productReducer);

4. 组件中按需加载模块

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

示例:加载 user 模块
typescript 复制代码
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
typescript 复制代码
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 的方法
typescript 复制代码
store.removeReducer = (key: string) => {
  if (store.asyncReducers[key]) {
    delete store.asyncReducers[key];
    store.replaceReducer(createRootReducer(store.asyncReducers));
  }
};
示例:移除 user 模块
typescript 复制代码
store.removeReducer('user');

7. 优势

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

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

相关推荐
风清云淡_A10 分钟前
【es6】原生js在页面上画矩形层级等问题的优化(二)
前端·es6
vvvae123426 分钟前
JavaScript 前端开发操作指南
开发语言·javascript·ecmascript
前端Hardy1 小时前
HTML & CSS 魔法秀:打造翻转卡片登录注册页面
javascript·css·html
努力挣钱的小鑫1 小时前
【Vue3】弹窗添加鼠标hover上边缘左、下的的拉伸宽度高度操作
javascript·vue·计算机外设
hvinsion2 小时前
HTML 霓虹灯开关效果
前端·html
顾平安2 小时前
JS 预编译代码实例分析
前端·js
雾恋2 小时前
leaferjs元素的基本使用
前端·javascript·vue.js
好奇的菜鸟2 小时前
Vue.js 实现用户注册功能
前端·javascript·vue.js
是程序喵呀2 小时前
vue安装步骤
前端·javascript·vue.js
前端Hardy2 小时前
HTML 中 a 标签跳转问题总结:从框架页面跳转的困境与突破
前端·javascript·html