在 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 包 的方案,兼顾开发效率与生产环境稳定性。