说在前面,这不仅是一篇 3.0 的功能介绍,更是阐述背后变更的原因,以及设计的逻辑,如果你正在开发自己的组件库,期望能够帮助到您 。
没错,3.0 她来了,3.0 是一个吉利的数字,她比2大1,但又比4小1,就是那么神奇,不是嘛!(⊙o⊙)…
v2 版本似乎发布了没有多久,为什么直接调到了 v3,主要原因是:
她有较多的更新,同时有一些是无法向下兼容的,例如,使用 dayjs 替换 momentjs
v2 版本主要是我们为了兼容 Vue 3 开发的一个版本,他没有太多的新特性,少数的破坏性更新也只是为了更好的兼容 Vue 3 的,对于一些新的功能和交互只能推迟到 v3 版本来实现了。
3 真的是一个吉利的数字,他刚好和 vue 3 一致,不会让一些人误解,未来的 4 5 6......,也只会出现“我都使用 4 了,你们还在用3” 的说法,不得不说,这的确可以减少一些答疑成本(太机智了)
团队开始全职维护该组件库,节奏会比以往要快很多,前方加速,请系好安全带(更新快了,你说我不稳定,更新慢了,你说我不维护,太 TM 难了)
说回正经事儿
3.0 都更新了啥?我想从源码层面、功能层面、易用性层面、性能层面分别讲解。
源码层面
使用 TS + Composition Api 进行重构,目前只有极个别的组件还在使用 Option Api,我们会逐步重构,但这些组件不会有破环性更新,所以不用担心未来的升级成本。搭配 Volar 你会得到更好的类型提示。不得不说 Vue 3 在 TS 方面有了很大的提升,但依然还有一些不足,对组件库来说体感较强的是 Vue 3 源码类型复杂度较高,泛型组件不友好,组件属性类型复用不友好,这三个问题分开来看都有一些方案去解决,但三者结合起来,我们依然没有找到一个非常好的平衡点去规范它,当然,我们在 TS 方面也并不深入,如果你比较擅长,也非常欢迎加入我们,一起为 Vue 3 生态,为开源贡献一份自己的力量。
目前我们对于组件属性的类型定义方案是把 props 单独抽出来定义成一个函数,举个例子:
importtype{ExtractPropTypes,PropType}from'vue';exporttypeButtonTypes='primary'|'ghost'|'dashed'|'link'|'text';exportconstbuttonProps=()=>({type:{type:StringasPropType<ButtonTypes>}})exporttypeButtonProps=Partial<ExtractPropTypes<ReturnType<typeofbuttonProps>>>;exportdefaultdefineComponent({name:'AButton',props:buttonProps(),})
通过这种方式,我们同时导出 buttonProps
ButtonProps
, 前者一般是给其他组件或用户二次封装组件使用,后者一般是用户写业务代码使用,至于前者为何是函数返回,这里涉及到一个引用类型默认值的问题,就是那个Vue 2 时代经典的面试题,为何定义 data 要用函数返回是一样的道理。通过这种方式算是达到了类型复用的目的。如果你有更好的建议,欢迎通过 PR 的方式讨论,你可以从一个简单的组件开始。
功能层面
简单一句话就是,同步了 antd 4.x 的功能:
1、自定义时间库,使用 dayjs 作为默认时间库,提供 momentjs、date-fns 快速切换功能
2、Tree、TreeSelect 支持虚拟滚动,Table 支持表格合计
3、更好的暗黑主题
4、更好的无障碍辅助
5、支持 RTL,除了少量未重构组件尚不支持,正式版之前添加
6、CSS Variables 正式版之前添加
更多细节功能不再展开叙述。
易用性层面
antd 经历了默认好看,到现在一直在追求的默认好用,但好用包含了功能强大、API 简单,这两个本身就是矛盾的存在,应该说我们一直探索的是他们之间的平衡点,是如何在扩展更多功能的时候,保持 API 的简单易用。
本次主要更改了 Tree
、TreeSelect
、Table
、Card
的自定义渲染时的API,我们以Table举例,
<template><a-table:dataSource="dataSource":columns="columns"/></template><script>exportdefault{setup(){return{dataSource:[{key:'1',name:'王二蛋',},],columns:[{title:'姓名',dataIndex:'name',},],};},};</script>
渲染结果如下:
很简单,对不对,但王二蛋是一位坐拥亿万资产的富二代,也是我们的VIP客户,老板期望对该类客户进行特别标示,名字要加粗加红加V加...,怎么玩,在 React 里面,我们可以直接:
name:<divclass="jia_cujia_hongjia_v">王二蛋</div>
当然在 Vue 中,你依然可以使用同样的方式进行配置,前提是使用 vue-jsx 语法或手写 render 函数,但 template 语法会让我们得到更好的性能,比 react 还要好的性能。那么在 template 中怎么配置,在 1.x 和 2.x 版本中我们约定了一个规则是在 columns 中通过 slots 指定一个"任意"名称的插槽进行配置:
<template><a-table:dataSource="dataSource":columns="columns"><templatev-slot:customName="{record,text}"><templatev-if="record.name==='王二蛋'"><divclass="jia_cujia_hongjia_v">王二蛋</div></template><templatev-else>{{text}}</template></template></a-table></template><script>exportdefault{setup(){return{dataSource:[{key:'1',name:'王二蛋',},],columns:[{title:'姓名',dataIndex:'name',slots:{customRender:'customName'//名称任取}},],};},};</script>
似乎看着没有什么问题,的确这种方式陪伴了我们好多年,似乎大家也都已经习惯了这种配置方式,但是在权衡了很久之后,我们依然决定打破这个舒适圈,这种方式的 API,他有两个弊端:
1、配置膨胀,如果你的每一列都需要自定义渲染,你需要在 columns 中都要配置上 slots,也要相应的在 template 提供对应的插槽。
2、才是最主要的问题,他有一个很大的风险就是,我们无法限制用户自定义的插槽名称,那么如果未来组件内部需要扩展插槽,就会有冲突的风险,这是一个不可控的风险。
先插一句,防杠精,其实我们是提供了 a-table-column 方式去构建配置的,这种方式并不需要去配置插槽,但我们并没有主推这种方式:
原因一是个人感觉 columns 要比 a-table-column 少写很多字符,也更直观些;
原因二是 columns 的形式,相较于 a-table-column 性能略高,其实 a-table-column 内部依然会转化为 columns,转化的过程目前有两种方式,第一种是父组件 table,遍历子组件 a-table-column,生成 columns;第二种是子组件 onMounted 之后,将子组件信息告诉父组件,这里还有个恶心的点是,构建子组件在父组件的位置索引,Vue 是不承诺渲染"过程顺序"的,只保证"结果顺序",这个索引的构建方法我就不展开了;除此之外,上述两种方法其实都用了非Vue文档API,都是有一定的风险的。总之,因为大部分用户选择了 columns,我们期望将 columns 的使用难度降低,让table更加易用。
所以 3.0,我们废弃了 column.slots 的配置,而是提供了统一的自定义出口 v-slot:bodyCell
,使用新的 API 优化后的代码如下:
<template><a-table:dataSource="dataSource":columns="columns"><templatev-slot:bodyCell="{record}"><templatev-if="record.name==='王二蛋'"><divclass="jia_cujia_hongjia_v">王二蛋</div></template></template></a-table></template><script>exportdefault{setup(){return{dataSource:[{key:'1',name:'王二蛋',},],columns:[{title:'姓名',dataIndex:'name',},],};},};</script>
插槽中可以不需要 v-else
,组件内部会自动 fallback 到默认值(王二蛋) ,正如我前文所说,当我们新增插槽的时候,会有和你自定义插槽冲突的风险,希望你没有起名叫 bodyCell 的插槽,除此之外,headerCell
customFilterDropdown
customFilterIcon
都是新增的插槽,我想应该大概可能不会那么巧就碰上了吧。
同样的逻辑,我们优化了 Tree、TreeSelect 自定义 title 的逻辑,不再需要在 treeData 数据源中通过 slots 配置自定义插槽,而是由 v-slot:title
统一接管。Card 自定义tab,不再需要在 tabList 数据源中通过 slots 配置自定义插槽,而是通过 v-slot:customTab
接管。
性能层面
1、FormItem 使用 provide/inject 代替 cloneElement,减少 render 次数,提升表单性能
2、废弃 TreeNode、TreeSelectNode 使用 treeData 属性替代
3、在 2.x 版本中,Select、AutoComplete 支持了虚拟滚动,在 3.0 中,我们又新增了 Tree、TreeSelect 组件支持虚拟滚动。可以轻松应对大数据渲染。
4、其它很多用户无感知的组件内部性能优化
为什么 Table 还不支持虚拟滚动?
两个原因:破坏性更新 和 成本 问题
如果要支持完善的虚拟滚动,会有很大的成本,注意,这里说的是完善的虚拟滚动,不是简单一个列表,我们要支持固定列、行列合并、展开、子表格等等一系列问题,开发成本完全不亚于一套组件库了,这也是为什么 MUI(原名 material ui 7万+ star 的开源项目) 专门招聘了全职人员维护虚拟表格组件。
虽然你在 antd react 版本文档中有一个虚拟滚动的示例,但那完全是借助第三方组件自定义的一个简单虚拟滚动示例,如果要添加固定列、选择、拖拽、展开等功能,成本非常高,你可以尝试在 antd react 版本的 issue 下搜索 table 虚拟
关键词查看相关问题。
对于Table,我们也会推出独立的产品去解决大数据展示问题,初版应该不会让大家等太久,如果顺利,最近一两个月会和大家见面,敬请期待! 可以关注下官方公众号,我们会及时通过公众号发布通知。