在 umi4 中复用 umi3 的组件

8 阅读5分钟

在 umi4 中复用 umi3 的组件

在 umi4 中复用 umi3 的 zb组件需解决 模块兼容性、依赖版本冲突、样式隔离 三大核心问题,以下是分场景的具体实现方案:

一、前置条件:组件可复用的前提

复用前需确认 umi3 组件满足以下条件(否则需先改造):

● 无强依赖 umi3 特有 API:如 dva 的 connect、useSelector(需替换为通用状态管理方案);

● 样式无全局污染:避免直接修改 antd 全局样式(如 @primary-color),推荐使用 CSS Modules 或 styled-components;

● 依赖版本可兼容:umi3 组件依赖的 react、antd 等库版本需与 umi4 项目对齐(或通过别名处理冲突)。

二、核心实现方案

方案 1:直接引用本地组件(开发阶段)

适合开发调试阶段,快速验证组件复用性(无需打包)。

操作步骤

1.  目录结构调整:

将 umi3 的 zb 组件目录(如 src/zb-components)复制到 umi4 项目的 src/legacy-components 目录下(避免与新组件命名冲突):

umi4-project/
├── src/
│   ├── legacy-components/  # umi3 旧组件存放目录
│   │   ├── BaseButton.tsx
│   │   └── DeviceStatus.tsx
│   └── pages/

2.  处理依赖冲突:

umi3 组件可能依赖旧版 react(如 17.x)或 antd(如 4.x),而 umi4 项目使用新版(如 react 18、antd 5)。需在 package.json 中配置 resolutions(pnpm/yarn)或 overrides(npm)强制使用新版依赖:

{
  "resolutions": {
    "react": "18.2.0",
    "antd": "5.13.0"
  }
}

3.  样式兼容:

umi3 组件若使用 less 样式(antd4 默认),需在 umi4 中安装 @umijs/plugin-less 并启用(umi4 已内置):

// umi4 配置文件 src/config.ts
import { defineConfig } from 'umi';
export default defineConfig({
  extraLessPlugins: ['less-plugin-prefixer'], // 可选:添加样式前缀避免冲突
  theme: {
    'primary-color': '#1677ff' // 与 umi3 zb 主题保持一致
  }
});

4.  状态管理适配:

若 umi3 组件使用 dva 管理状态(如 connect(mapStateToProps)),需替换为 umi4 支持的轻量方案(如 jotai):

// umi3 旧组件(dva 版本)
import { connect } from 'dva';
const DeviceList = (props) => <div>{props.devices}</div>;
export default connect(({ device }) => ({ devices: device.list }))(DeviceList);

// umi4 适配后(jotai 版本)
import { useAtomValue } from 'jotai';
import { devicesAtom } from '@/store/device'; // 新状态管理原子
const DeviceList = () => {
  const devices = useAtomValue(devicesAtom);
  return <div>{devices}</div>;
};
export default DeviceList;
方案 2:封装为独立 NPM 包(生产环境)

适合长期复用,将 umi3 组件打包为独立包,供 umi4 项目安装使用(推荐)。

操作步骤

1.  组件库打包配置:

在 umi3 项目中新建 packages/zb-components 目录,作为独立组件库。使用 rollup 或 tsup 打包(推荐 tsup,配置更简单):

// tsup.config.ts
import { defineConfig } from 'tsup';
export default defineConfig({
  entry: ['src/components/**/*.tsx'], // 需复用的组件入口
  format: ['esm', 'cjs'], // 输出 ESM 和 CommonJS 格式
  dts: true, // 生成类型声明文件
  clean: true,
  external: ['react', 'react-dom', 'antd'], // 排除外部依赖(由宿主项目提供)
  css: true // 打包样式(若组件含 CSS/LESS)
});

2.  发布到内部 NPM 仓库:

打包后通过 npm publish 发布到团队内部 NPM 仓库(如 verdaccio),umi4 项目通过 npm install @benny/zb-components@1.0.0 安装。

3.  在 umi4 中使用:

// umi4 页面组件
import { BaseButton, DeviceStatus } from '@benny/zb-components';

const IotToolPage = () => (
  <div>
    <DeviceStatus status="online" /> {/* 复用 umi3 的设备状态组件 */}
    <BaseButton type="primary" onClick={() => console.log('点击')}>
      提交配置
    </BaseButton>
  </div>
);

4.  处理样式冲突:

若 umi3 组件的样式类名(如 ant-btn)与 umi4 的 antd5 冲突,可通过以下方式隔离:

a.  CSS 作用域:在组件库打包时添加样式前缀(如 zb-),或使用 CSS Modules;

b.  全局样式覆盖:在 umi4 项目的 src/global.less 中重置冲突样式:

// 仅影响 zb-components 下的样式
.zb-components {
  .ant-btn {
    border-radius: 4px; // 与 umi3 保持一致
  }
}
方案 3:模块联邦(高级场景)

适合跨项目实时共享组件(如 umi3 和 umi4 项目同时开发),通过 Webpack 5 的模块联邦(Module Federation)实现。

操作步骤

1.  umi3 项目配置(作为宿主):

修改 umi3 的 config/config.ts,暴露需共享的组件:

// umi3 config/config.js
export default {
  webpack5: {}, // 启用 webpack5
  mfsu: false, // 关闭 mfsu(与模块联邦冲突)
  chainWebpack(config) {
    config.plugin('ModuleFederation').use(require('webpack').container.ModuleFederationPlugin, [
      {
        name: 'zb', // 应用名称
        filename: 'remoteEntry.js', // 入口文件
        exposes: {
          './BaseButton': './src/components/BaseButton.tsx', // 暴露组件
          './DeviceStatus': './src/components/DeviceStatus.tsx'
        },
        shared: { react: { singleton: true }, 'react-dom': { singleton: true } } // 共享依赖
      }
    ]);
  }
};

2.  umi4 项目配置(作为消费者):

在 umi4 的 src/config.ts 中配置远程引用:

// umi4 src/config.ts
import { defineConfig } from 'umi';
export default defineConfig({
  webpack5: {},
  chainWebpack(config) {
    config.plugin('ModuleFederation').use(require('webpack').container.ModuleFederationPlugin, [
      {
        name: 'iotTool',
        remotes: {
          zb: 'zb@http://localhost:8001/remoteEntry.js' // umi3 项目地址
        },
        shared: { react: { singleton: true }, 'react-dom': { singleton: true } }
      }
    ]);
  }
});

3.  在 umi4 中动态加载组件:

// umi4 页面组件
const BaseButton = React.lazy(() => import('zb/BaseButton'));
const DeviceStatus = React.lazy(() => import('zb/DeviceStatus'));

const IotToolPage = () => (
  <React.Suspense fallback="加载中...">
    <DeviceStatus status="online" />
    <BaseButton type="primary">提交</BaseButton>
  </React.Suspense>
);

三、关键注意事项

1. 依赖版本对齐

● 优先将 umi3 组件的 react、antd 版本升级至与 umi4 一致(如 react 18、antd 5),避免因版本差异导致的 PropTypes 警告或功能异常;

● 若无法升级,可通过 npm alias 处理(如 npm install react@npm:react@17.x),但可能增加维护成本。

2. 状态管理解耦

● 避免在组件中直接使用 dva 的 dispatch 或 useSelector,需将状态逻辑抽离为独立的 hooks(如 useDeviceList),由 umi4 项目通过 jotai/zustand 重新实现;

● 示例:

// umi3 旧组件(解耦前)
const DeviceList = () => {
  const { dispatch } = useDva();
  const devices = useSelector((state) => state.device.list);
  return <div>{devices}</div>;
};


// 解耦后(通用版本)
const useDeviceList = () => {
  const devices = useAtomValue(devicesAtom); // 由 umi4 提供状态
  const fetchDevices = useCallback(() => { /* 接口调用逻辑 */ }, []);
  return { devices, fetchDevices };
};
const DeviceList = () => {
  const { devices } = useDeviceList();
  return <div>{devices}</div>;
};
3. 样式隔离

● 对强依赖 antd 样式的组件(如 Form.Item),需确保 umi4 项目的 antd 主题与 umi3 一致(通过 ConfigProvider 同步 token);

● 对自定义样式(如 device-status-indicator),推荐使用 CSS Modules 或 styled-components,避免全局污染。

四、验证与测试

1.  功能测试:在 umi4 中调用复用组件,验证点击、表单提交等核心功能是否正常;

2.  样式测试:对比 umi3 和 umi4 中组件的样式(如颜色、边距),确保一致性;

3.  性能测试:通过 umi build 检查打包体积,避免因重复依赖导致体积膨胀(推荐使用 source-map-explorer 分析);

4.  兼容性测试:在主流浏览器(Chrome、Firefox)中验证组件渲染,确保无 React 18 不兼容问题(如 legacy 模式警告)。

通过以上方案,可实现 umi4 对 umi3 zb 组件的高效复用,同时保持代码的可维护性和扩展性。优先推荐 封装为独立 NPM 包 的方案,兼顾开发效率与生产环境稳定性。