注:本篇旨在记录并回顾团队项目进行Ant Design V4版本升级的全过程,及过程中所遇到的一些问题,不涉及深入的技术分析,以期为面对类似情况的开发者提供一些思路及参考。
今年(2020年)年初,Ant Design发布了4.0大版本更新。新的V4版本带来了许多值得关注的优化及改变,如升级调整后的设计规范、全局范围的性能优化、顺应潮流引入的暗黑主题等等。经过社区半年多的实践反馈,也该是时候让我们的项目踏上V4升级之旅了。
开始之前
在决定实施升级之前,我们需要先(针对我们团队的前端项目)解答以下两个问题:
为什么要升级?
Ant Design仍然是我们团队的前端项目(以下简称"项目")中使用最广泛的UI库;
V4版本对项目目前经常使用的一些组件进行了重构,修复了遗留问题,并带来了性能优化;
V4版本提供了一些新的功能,这些功能对我们来说很有引入的价值;
V4版本开始使用React Hooks来重写部分组件,这与项目目前的代码风格趋势一致。
为什么现在升级?
项目一直使用在使用V3版本,但社区对V3的维护已于2020年6月停止,继续使用一个废弃的版本,风险不言而喻;
V4发布的时间已经足够长,大部分的基础问题都已得到解决,不需要担心踩坑的危险;
团队计划对项目网站的UI风格进行统一和优化,先进行升级便于协调后续安排。
变更要点一览
关于Ant Design V4的升级详情,大家可以参考官网文档,以下是部分要点概括,便于大家了解概况:
设计规范调整:包括基础风格和样式的调整,以及新的暗色主题效果、无边框风格组件;
兼容性调整:涉及对IE的兼容性调整,更需要关注的是V4版本更多的使用了Hooks来简化代码;
移除废弃的API:移除了一些在V3版本中提示Warning信息的废弃API;
图标升级:通过引入
SVG
图标,减少了antd的默认包大小;组件重构:主要包括对
Form
和Date Picker
组件的重写,以及新的Tree
、Select
、TreeSelect
、AutoComplete
组件,同时,引入虚拟滚动,优化了承载大量选项/项目组件的渲染性能。
准备升级
为了使升级尽可能的简单平滑,Ant Design V4已尽可能保证了最大程度的兼容性,但仍有一些问题需要特别处理,其中涉及到一些准备工作:
升级antd版本:需要先将antd的版本升级到
3.26.18
(3.x的最新版本),并根据控制台的Warning信息删除/修改相关API。确认React版本:文档建议的版本是
16.12.0
以上,而项目目前使用的版本是16.9.0
,理论上需要升级,但考虑到antd仍能够支持16.9.0
版本,为了减少升级的工作量,避免引入更多的风险,我们决定暂不升级React。
开始升级
核心思路
首先,我们尝试使用Ant Design团队提供的codemod cli
工具(@ant-design/codemod-v4)进行迁移,其可以帮助我们解决大多数基础问题。
通过运行命令:
npx -p @ant-design/codemod-v4 antd4-codemod src
工具可以自动完成部分迁移,并对不能自动迁移的部分进行提示说明。
而后,根据提示逐一手动迁移不能自动完成的部分,待这些提示部分手动迁移完成后,再次执行上述命令重新确认。
如此循环往复,直到完成所有的迁移工作。
升级详情
以下为项目在升级过程中涉及到的一些代码变更示例:
Icon
从@ant-design/icons
中引入特定icon
- import { Icon } from 'antd'
+ import { ExclamationCircleFilled } from '@ant-design/icons'
- <Icon type="exclamation-circle" theme="filled" />
+ <ExclamationCircleFilled />
从@ant-design/icons
中引入Icon
组件,以构建自定义图标
- import { Icon } from 'antd'
+ import Icon from '@ant-design/icons' <Icon component={svg} />
Select
对于Select
组件, filterOption
第二个参数直接返回原数据,不在需要通过option.props.children
来进行匹配。 Option
的value
属性改为必传参数。
<Select
showSearch
style={{ width: 200 }}
placeholder="Select a person"
optionFilterProp="children"
onChange={onChange}
onFocus={onFocus}
onBlur={onBlur}
onSearch={onSearch}
filterOption={(input, option) =>
- option.props.children
+ option.children
.toLowerCase()
.indexOf(input.toLowerCase()) >= 0
}
>
<Option
+ value="jack"
>
Jack
</Option>
</Select>
AutoComplete
AutoComplete
选项与Select
一致,需要使用options
代替dataSource
。
<AutoComplete
- dataSource={this.state.loadOptions}
+ options={this.state.loadOptions}
onSelect={this.onSelect}
onChange={this.triggerSearch}
optionLabelProp={'value'}
placeholder="Search by Name"
/>
Pagination
Pagination
自4.1.0
版本起,会默认将showSizeChanger
参数设置为true
,因而在数据条数超过50时,pageSize
切换器会默认显示。这个变化同样适用于Table
组件。
<Pagination
+ showSizeChanger={false}
/>
类名变化
通常来说,开发工作并不需要太关注类名变化,但由于我们的Cypress Testing脚本在定位DOM节点时大量使用了antd类名,对于一些重构后的组件,我们详细对比了更改前后的类名变化,此处不再赘述。
样式变化
由于项目前期在使用antd组件时,或多或少地使用了:global()对默认样式进行了重写,导致部分页面同类型组件却出现了不同样式,为了便于升级时调试,也为了之后能够进行进一步的全站样式统一优化,我们去掉了大部分的样式重写,与antd的默认样式保持一致。
主要涉及:Input
,Dropdown
,Select
,Calendar
,TimePicker
,DatePicker
? height: 42px、44.8px、48px -> 40px
? font-size: 14px -> 16px
? margin-bottom: 15px、16px、30px -> 16px
问题回顾
ESlint
- moment:在升级antd之后,我们发现部分类型报错,经查确认是
moment
版本问题导致,最低支持的版本为2.25.3
;
// Type 'Moment | undefined' is not assignable to type 'Moment | null | undefined'. // Type 'Moment' is missing the following properties from type 'Moment': isoWeeksInISOWeekYear, tz
- @types/react、@types/react-dom:同时我们也发现很多难以定位的类型问题,在尝试升级
@types/react
、@types/react-dom
后,问题得到解决。
src/lib/components/Icons/index.tsx:147:8
-error TS2741: Property 'translate' is missing in type '{children?: ReactNode; color?: string | undefined; component: FC<{}>; className: string;}' but required in type 'Pick<IconComponentProps, "max" | "required" | ... 350 more ... | “onTransitionEndCapture">'.
147 <Icon...
本地无法启动
升级后,项目在本地开发环境无法启动,控制台出现报错:
经常确认是升级moment
版本导致,修改webpack.config.js
配置,移除moment.js
设置后问题解决。
module: {
// noParse: /moment\.js/,
}
功能失效
Popover
和Tooltip
组件在升级后报类型错误,经查确认为antd bug,于是在Github创建了issue,该问题在antd最新的release已经得到修复。
<Popover
trigger="hover"
placement="bottomLeft"
>
<WarningOutlined className={warningIconClass} />
<WarningOutlined className={warningIconClass} />
</Popover>
// This JSX tag's 'children' prop expects a single child of type 'ReactElement<any, string | ((props: any) => ReactElement<any, string | ... | (new (props: any) => Component<any, any, any>)> | null) | (new (props: any) => Component<any, any, any>)> | undefined', but multiple children were provided.
单元测试失败
目前,我们的项目主要使用react-testing-library
编写进行单元测试,在升级后,部分UT失败,经查主要是以下两个原因导致:
类名改变,选择器失效无法定位元素,导致断言失败;(修改选择器修复)
由于因加入的虚拟滚动功能,
Select
,Dropdown
,AutoComplete
等组件在获取下拉菜单选项时,无法获取当前未在屏幕上显示的元素,导致断言失败;(修改逻辑,先输入操作保证元素显示后再获取元素,修复)
+function doSelectDropdownOption(
+ labelText: string,
+ selectOption: string
+) {
+ const selectLabel = getByLabelText(labelText)
+ fireEvent.click(selectLabel)
+ act(() => {
+ fireEvent.input(
+ selectLabel.parentElement?.querySelector('input')!,
+ {
+ target: { value: selectOption },
+ }
+ )
+ })
+ fireEvent.click(getDocTypeMenuItem(selectOption)!)
+}
it('test', () => {
fireEvent.click(getByText('Upload Document'))
const uploadBtn = getByText('Upload')
// The upload button should be disabled until we have chosen a load
expect(uploadBtn).toBeDisabled()
doSelectFile()
// Select a document type
+ doSelectDropdownOption('Document Type', 'Other (Load)')
expect(uploadBtn).toBeDisabled()
})
升级完成
至此,升级工作就算是告一段落了,但对我们来说,后续要做的事情还有很多,如升级React到推荐版本16.12.0
、规范并统一组件风格样式、完善组件库/文档等等。整个升级过程虽然不存在太高的技术难度,但却是一份非常费时费力的工作,我们不得不踩入团队自己挖下的坑,因此,为了避免重蹈覆辙,我们在代码规范上又加上了两条:
保持对第三方依赖库的规律性升级;
保证项目中页面的自定义样式尽可能一致。
这也算是另一个收获了。
“卓派前端工作志,聚焦实用前端技术,让编程更有趣!”
前端技术组 @ 西安卓派科技 NEXT Trucking — 拉勾 | Boss | 知乎 | 掘金 | 简书