背景
在一次需求开发中,有个功能的实现是需要用到支持虚拟滚动的树形UI组件,看了下项目之前用的是antd3.x的版本,而antd4.x中的tree组件才支持虚拟滚动。
因此,我想到了两个解决方案:
- 项目中同时引入antd3.x和antd4.x
- 将旧有的antd3.x升级到4.x
一路下来踩了不少坑,这里做个记录分享给大家。
方案一、antd3.x & 4.x混用
① 别名安装
因为直接安装antd4.x会覆盖掉之前的版本,所以需要通过别名安装antd4.x
yarn add antd4@npm:antd@4
安装成功后可以看到package.json中多了antd4的库,node_modlues下也能看到名为antd4的目录
接下来便可以在组件中引入antd4的组件了,为了方便区分,在引入时也可以对组件进行重命名,因为原项目用的是umi中的antd3,样式文件会自动引入,所以这里我们要自己手动引入antd4的样式。
import { Button } from 'antd';
import { Button as Button4 } from 'antd4';
import "antd4/dist/antd.css";
const MixButton = () => {
return (
<>
<Button>antd3_button</Button>
<Button4 type="text>antd4_button</Button4>
</>
)
}
export default MixButton;
我们看到两个button都能展示出来,但是antd4的新特性textButton样式却没有生效
(官方的textButton是没有底色,鼠标悬浮上去才会有底色)
打开控制台一看发现,虽然有ant-btn-text这个样式类,但是样式被覆盖掉了。
原来是antd3的样式和antd4的样式起了冲突。
② ConfigProvider配置
如何隔离开antd3和antd4的样式,这个问题困扰了我很久,总不可能一个个手动加上吧。。
在查阅各种资料后,发现官网中ConfigProvider 可以通过设置prefixCls来改变antd样式的统一前缀。
再回到我们的代码
import { Button } from 'antd';
import {
Button as Button4,
ConfigProvider as Antd4ConfigProvider
} from 'antd4';
import "antd4/dist/antd.css";
const MixButton = () => {
return (
<Antd4ConfigProvider prefixCls="antd4">
<Button>antd3_button</Button>
<Button4 type="text">antd4_button</Button4>
</Antd4ConfigProvider>
)
}
export default MixButton;
我们能看到样式的前缀都已经变成了antd4
当然我们引入的antd样式里的前缀还是还是ant,所以接下来还需要修改样式中的前缀。关于这里我也踩了好多坑尝试了几种方案
③ 样式隔离(less变量覆盖)
我们可以建立一个单独的 less 变量文件,引入这个文件覆盖 antd.less 里的变量。
@import '~antd4/es/style/themes/default.less';
@import '~antd4/dist/antd.less'; // 注意这里目录名是我们别名安装后的目录,要跟node_modules中的一致
@ant-prefix: antd4;
然后我们在全局引入(umi中是global.less),如果想要在组件内引入,记得设置disableCSSModules: true
// global.less
@import "./my-antd.less";
运行代码可以看到样式成功隔离了
③ 样式隔离(编译css)
我还尝试了另一种方案,有点复杂这里只做记录,并不推荐
node_modules下 antd4/dist/.css实际上是通过less文件编译生成的,看过源码的同学应该可以注意到在variable.less下就有@ant-prefix这个变量。
我们可以通过lessc命令修改@ant-prefix并编译生成新的css文件
lessc --js --modify-var='ant-prefix=antd4' node_modules/antd4/dist/antd.varbliless antd4.css
// 这里对应antd4在node_modules中的位置 输出的文件名
没有全局安装less也可以在package.json中添加命令利用npm命令来运行
// package.json
"scripts": {
"myLessc": "lessc --js --modify-var='ant-prefix=custom' node_modules/antd4/dist/antd.less custom.css"
}
// shell
npm run myLessc
运行完命令后可以在根目录下看到编译后的文件antd4.css 里面前缀都变成了antd4
在引入新的样式文件后,antd4按钮的样式成功与antd3隔离了
import { Button } from 'antd';
import {
Button as Button4,
ConfigProvider as Antd4ConfigProvider
} from 'antd4';
// import "antd4/dist/antd.css";
import "@/antd4.css";
const MixButton = () => {
return (
<Antd4ConfigProvider prefixCls="antd4">
<Button>antd3_button</Button>
<Button4 type="text">antd4_button</Button4>
</Antd4ConfigProvider>
)
}
export default MixButton;
当然umi项目默认开启了css modules模式,是不支持直接引入css文件的,需要在config中设置disableCSSModules: true才能生效
在经历这么一番折腾后,终于实现了样式隔离。。
③ 样式隔离(配置less-loader)
前面的方法都是全量引入样式,当然有人会觉得这么做既麻烦又不优雅,为什么不直接用less-loader按需加载呢?
还记得上面说到别名安装antd4后,样式文件是通过手动引入的吗
import "antd4/dist/antd.css";
其实在这一步就可以通过babel-plugin-import来实现按需加载组件样式
对于umi项目来说需要在config.js文件中添加配置
export default {
...
extraBabelPlugins: [
[
"import",
{
libraryName: "antd4", // 指定要按需加载的库的名称
libraryDirectory: "es", // 告诉插件从哪个子目录中按需加载模块。
style: true // 指定是否加载样式文件,以及样式文件的加载方式。
// true:加载对应模块的 CSS 文件。
// "css":加载对应模块的 CSS 文件。
// "less":加载对应模块的 Less 文件
}
]
],
}
然后需要通过less-loader的modifyVars来对antd4中的@ant-prefix进行修改
// config.js
export default {
...
lessLoaderOptions: {
javascriptEnabled: true,
modifyVars: {
'@ant-prefix': 'antd4',
},
},
}
umi项目也可以直接设置theme配置项
// config.js
theme: {
'@ant-prefix': 'antd4'
},
但这样问题就来了,因为lessLoader会对所有less文件都做处理,因此antd3的变量也会被修改到,所以antd3 button的样式又失效了。。。
那么可以在配置less-loader时用include限制处理的范围,umi中配置webpack使用的是chainWebpack,写法如下
chainWebpack: config => {
config.module
.rule('my-less')
.test(/\.less$/)
.include.add(path.resolve(__dirname, '../node_modules/antd4'))
.end()
.use('style-loader')
.loader('style-loader')
.end()
.use('css-loader')
.loader('css-loader')
.end()
.use('less-loader')
.loader('less-loader')
.options({
modifyVars: {
// 'primary-color': '#1DA57A',
'ant-prefix': 'antd4',
},
javascriptEnabled: true,
});
}
重新运行项目,不出意外的出意外了,控制台报错
升级了less-loader的版本还是不行。。不知道是不是跟umi的af-webpack有关。
我拿非umi项目试了一下,这个办法是行的通的,只能说umi不想让我好过QAQ
目前就在这卡住了。。后面有时间再来研究研究
再次尝试(2024.8.24)
试了一下umi项目只要去掉style-loader和css-loader就可以成功运行了!(webpack项目还是正常的配置webpack就行啦)
chainWebpack: config => {
config.module
.rule('my-less')
.test(/\.less$/)
.include.add(path.resolve(__dirname, '../node_modules/antd4'))
.end()
// .use('style-loader')
// .loader('style-loader')
// .end()
// .use('css-loader')
// .loader('css-loader')
// .end()
.use('less-loader')
.loader('less-loader')
.options({
modifyVars: {
// 'primary-color': '#1DA57A',
'ant-prefix': 'antd4',
},
javascriptEnabled: true,
});
}
跑起来可以看到antd4的textButton以及primary-color的改动都生效了
总算是解决了这个问题,欣慰~
方案二、antd3.x 升级到 antd4.x
其实在混用踩了这么多坑之后,我其中尝试过升级方案,正好官网也提供了升级工具,详细可以参考官网教程 4x.ant.design/docs/react/…
安装好升级工具后直接运行即可
yarn global add @ant-design/codemod-v4
antd4-codemod src
对于antd4破坏升级的组件(如From),会自动引入@ant-design/compatible兼容包保持运行。
- import { Form, Input, Button, Mention } from 'antd';
+ import { Form, Mention } from '@ant-design/compatible';
+ import '@ant-design/compatible/assets/index.css';
+ import { Input, Button } from 'antd';
运行升级工具后,对于无法自动修改的部分会在控制台提示手动修改,修改完后可以再次运行命令来检查。
可以看到一堆文件都被升级工具修改了
运行完成后控制台会提示要添加对应的依赖。
在package.json中添加好再次安装即可
"dependencies": {
"antd": "^4.0.0",
"@ant-design/compatible": "^1.1.2",
"@ant-design/icons": "^4.0.0",
}
最后成功运行代码~
不过antd3和4有些写法并不兼容,比如说antd3中Row组件下的元素会尽可能占满一行,而antd4中只是占内部元素宽度。所以升级后还是需要去详细测试一遍的。