组件库重构,支持antd 4.x

逻辑清韵
• 阅读 4287

前言

React 15.x 升 React 16.x 是一次内部重构,对于使用者来说,原来的使用方式仍然可用,额外加了新的功能;而Antd 3.x 升 Antd 4.x, 在我的认知范围里,可以称作是飞(po)跃(huai)性的重构, 因为以前很多写法都不兼容了,组件代码重构,使用者的代码也得重构。但这次重构解决了3.x的很多问题,比如:

  • 由于Icon无法按需加载导致打包体积过大;
  • 由于Form表单项变化会造成其他表单项全量渲染,大表单会有性能问题;
  • 时间库moment包体积太大。

说这么多,还是直接来张图吧,我个人项目的打包体积变化:Antd 3.x VS Antd 4.x

组件库重构,支持antd 4.x

组件库重构,支持antd 4.x

升4.x之后,gzip少了150kb,也就是包大小少了500多kb,这不香么。

关于升级

我和我的小组,为了用更爽的方式来开发迭代,针对于antd的Form和Table等组件做了一些简单的二次封装,形成了组件库antd-doddle。虽然Antd 4.x推出快小半年了,受疫情影响,今年业务迭代比较缓慢,没有新系统,也觉得暂时没必要去重构业务代码,所以一直只关注不动手。最近比较闲,组件库针对Antd 4.0做了适应性重构,作为一个胶水层,最大程度的去磨平4.0版本Form这种破坏性变动,减少以后业务代码升级4.x版本的调整量。

Antd 4.x到底做了哪些变化,在官方文档可以看到。

这篇文章主要讲针对于4.x Form的变化,我重构组件库的思路。

Antd-doddle 2.x文档地址, 支持4.x: http://doc.closertb.site, 首次加载较慢,请耐心等候。

Antd-doddle 1.x文档地址, 支持3.x: http://static.closertb.site, 首次加载较慢,请耐心等候。

试用项目Git 地址

项目在线试用地址, 请勿乱造

FormGroup重构思路

Form组件变化

4.x 中除了Icon,最大的更改就在于Form,我自己感受到的变化是:

  1. 舍去了Form.create高阶组件包裹表单的写法,而改采用hooks或ref的方式去操作form实例;
  2. 以前每个表单组件的数据绑定是通过getFieldDecorator这个装饰方法完成,现在改由FormItem来完成;
  3. 初始values设置,以前是通过getFieldDecorator设置,并且是动态的,即value改变,表单值跟随改变。现在是在Form最外层设置,但是以defaultValue形式设置,即value改变,表单值不跟随变化;
  4. 最大的改变就是增量式更新,3.x版本,任意表单项改变,都会造成Form.create包裹的全部表单项重新render,这是非常大的性能消耗;而4.x之后,任意表单项改变,只有设置了shouldUpdate属性的表单项有可能执行render,类似于React 16新增的componentShouldUpdate

根据上面的变化点,由外向内层层剖析,针对性的做重构;

FormGroup的变化

由于以前FormGroup组件,除了收集form方法和公共配置,也作为一个标识,接管了组件内部的渲染层;3.x版本其form实例由Form.create,即业务代码提供;4.x与其相似,只不过是通过hooks生成form实例。

变化点主要在于4.x版本Form要提供initialValues的设置,且这是一个defaultValue的设置,所以我们需要拓展,让其支持values为异步数据时,表单项的值能跟随其改变, 其原理很简单,监听value的变化,并重置表单数据,实现代码如下:

// 伪代码,只涉及相关改动
const FormGroup = (props, ref) => {
  const { formItemLayout = layout, children, datas = {}, ...others } = props;

  // 兼容了非hooks 组件调用的写法,内部再声明一个ref, 以备用;
  const insideRef = useRef();
  const _ref = ref || insideRef;

  const formProps = {
    initialValues: {}, // why
    ...formItemLayout,
    ...others
  };
  // 如果datas 值变化,重置表单的值
  useEffect(() => {
    const [data, apiStr] = Type.isEmpty(datas) ? [undefined, 'resetFields'] : [datas, 'setFieldsValue'];
    // 函数式组件采用form操作;
    if (props.form) {
      props.form[apiStr](data);
      return;
    }
    // 如果是类组件,才采用ref示例更新组件
    if (typeof _ref === 'object') {
      _ref.current[apiStr](data);
    }
  }, [datas]);


  return (
    <Form {...formProps} ref={_ref}>
      {deepMap(children, extendProps, mapFields)}
    </Form>);
};

上面有句代码 initialValues: {},会让人困惑, 为什么没有赋值为datas呢;这个又是antd的一个隐藏知识点,举个例子:

form.setFieldsValue({ name: 'antd', age: 18 }); 

// 后面想清空
form.setFieldsValue({}); 

// 最后发现上面清空根本没生效,原因可以自己想想

所以当我们想做一些需求,比如先编辑一个表单,表单中有数据了;但没做操作关闭了,然后点了新增按钮,传了一个空对象,这事就发现bug了,上一次编辑的数据还在,除了这个,还有一些其他的业务场景会用到,在antd中也有一个类似的issue:

在表单里有很多元素且拥有initialValue的时候, 如何简单的清空表单

所以在这个组件设计上,就将initialValues默认置空数据,然后设置的数据采用setFieldsValue来重置。如果想清空表单,直接传入一个空数据,被组件检测到后,内部调用resetFields来实现。快夸一下天才的我。

FormRender的变化

相比于FormGroup的变化,子组件FormRender相比较就变化小一点,主要在适应第二点变动,用代码的方式更直观:

// 3.x
const render = renderType[type];
content = (
  <FormItem
    label={name}
    rules={gerateRule(required, pholder, rules)}
    {...formProps}
  >
   {getFieldDecorator(key, {
     initialValue: data,
     rules: gerateRule(required, pholder, rules)
   })(
    render({ field: common, name, enums: selectEnums, containerName }))}
  </FormItem>);

重构之后

// 4.x
const render = renderType[type];
content = (
  <FormItem
    name={key}
    label={name}
    rules={gerateRule(required, pholder, rules)}
    {...formProps}
  >
    {render({ field: common, name, enums: selectEnums, containerName })}
  </FormItem>);

联动表单的实现变化

主要实现三种联动,根据其他表单项的变化,来改变关联表单项

  • 是否渲染或改变渲染方式;
  • 是否禁用;
  • 校验规则

由于以前每次表单项变化,都会引起其他表单项render,所以可以暴力的通过FormGroup增加数据层(useRef)与监(bao)听(guo)每个表单项的onChange来实现;

新的Form新增了onFormChange回调来支持增量式数据收集,但第四点变动,让老的方案GG;表单项的联动需要依赖shouldUpdate来实现,这也是官方推荐的方案;

组件库重构,支持antd 4.x

其本质是,设置了shouldUpdate属性的FormItem,其仅仅作为一个容器,这个容器监听了类似onFormChange这种事件,然后根据shouldUpdate来判断是否需要重新渲染容器内的子元素,子元素渲染实现是一个应用React renderPrrops的设计模式;

所以联动方案似乎变得更简单了, 就多一层FormItem包裹,看部分代码实现:

const render = renderType[type];
content = shouldUpdate ? (
  <FormItem shouldUpdate={shouldUpdate} noStyle>
    {form => { 
      const datas = form.getFieldsValue();
      const require = typeof required === 'function' ? required(initData, datas) : required;
      const disabled = typeof disableTemp === 'function'
      ? disableTemp(initData, datas) : disableTemp;
      return finalEnable(initData, datas) ?
      (<FormItem
        key={key}
        name={key}
        label={name}
        dependencies={dependencies}
        rules={gerateRule(require, pholder, rules)}
        {...formProps}
        {...otherFormPrrops}
      >
        {render({ field: Object.assign(common, { disabled }), name, enums: selectEnums, containerName })}
      </FormItem>) : selfRender(datas, form)}
    }
  </FormItem>) : /* 非联动实现 */

其他

除了上面这些变化,其实还有很多边边角角的变化,比如:

  • 搜索框组件其实也是依赖FormGroup来实现的,所以内部也做了一些小调整,但业务代码就完全不需要做改动;
  • 以前支持了RangePicker 的数据自动组装,但由于4.x 支持DayJs 和 Moment 时间库切换,加上initValues的提前设置,这个功能暂时就取消了;
  • 样式文件的按需加载;

还有,还有一些未考虑好的的新增特性。

使用对比

实现一个小的编辑框,类似下面这样:

组件库重构,支持antd 4.x

重构前的代码

import React from 'react';
import { FormGroup } from 'antd-doddle';
import { editFields } from './fields';

const { FormRender } = FormGroup;

function Edit({ id, form, data }) {
  const { getFieldDecorator } = form;

  return (
    <FormGroup getFieldDecorator={getFieldDecorator} required>
      {editFields.map(field => <FormRender key={field.key} field={field} data={data} />)}
    </FormGroup>
  );
}

export default FormGroup.create()(Edit);

重构之后

import React from 'react';
import { FormGroup } from 'antd-doddle';
import { editFields } from './fields';

const { FormRender } = FormGroup;

function Edit({ id, data, ...others }) {
  const [form] = FormGroup.useForm();

  return (
    <FormGroup required form={form} datas={data}>
      {editFields.map(field => <FormRender key={field.key} field={field} />)}
    </FormGroup>
  );
}

export default Edit

不仔细看,是不是不易察觉到变化

重构感受

因为用的还不像3.x版本那么熟练,很多特性还没用上,所以先重构一个简易版本,来完成日常场景,后面再慢慢迭代。

如果感兴趣,可以fork项目或查看项目文档

文章原地址

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Easter79 Easter79
3年前
sql注入
反引号是个比较特别的字符,下面记录下怎么利用0x00SQL注入反引号可利用在分隔符及注释作用,不过使用范围只于表名、数据库名、字段名、起别名这些场景,下面具体说下1)表名payload:select\from\users\whereuser\_id1limit0,1;!(https://o
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
美凌格栋栋酱 美凌格栋栋酱
5个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
逻辑清韵
逻辑清韵
Lv1
独怜幽草涧边生,上有黄鹂深树鸣。春潮带雨晚来急,野渡无人舟自横。
文章
4
粉丝
0
获赞
0