Component library refactoring, support antd 4. X

Time:2021-6-9

preface

React 15. X to react 16. X is an internal reconstruction. For users, the original use mode is still available, and new functions are added; The upgrade of antd 3. X to antd 4. X, in my cognitive range, can be called the reconfiguration of Huai, because many of the previous writing methods are not compatible, component code reconfiguration, user code also have to be reconstructed. But this refactoring solves many problems of 3. X, such as:

  • Because icon can not be loaded on demand, the packing volume is too large;
  • Due to the change of form items, other form items will be rendered in full, and large forms will have performance problems;
  • The time base moment package is too large.

So much, let’s just take a picture of the packaging volume change of my personal projectAntd 3.x VS Antd 4.x

Component library refactoring, support antd 4. X

Component library refactoring, support antd 4. X

After 4. X, gzip is less than 150kb, that is, the packet size is less than 500kb. Isn’t that nice.

About upgrading

In order to develop iterations in a better way, my team and I have done some simple secondary encapsulation for the components of antd, such as form and table, to form the component library antd doddle. Although antd 4. X has been launched for nearly half a year, due to the impact of the epidemic, the business iteration this year is relatively slow, there is no new system, and there is no need to refactor the business code for the time being, so we have been focusing on it instead of doing it. Recently, the component library has made adaptive refactoring for antd 4.0. As a glue layer, the destructive change of form in version 4.0 has been smoothed to the greatest extent, and the adjustment amount of business code upgrade in version 4. X has been reduced.

What changes antd 4. X has made can be seen in the official documents.

This article mainly talks about the idea of refactoring component library for the change of 4. X form.

Antd doddle 2. X document address, support 4. X: http://doc.closertb.site , the first load is slow, please wait patiently.

Antd doddle 1. X document address, support 3. X: http://static.closertb.site , the first load is slow, please wait patiently.

Git address of trial project

Project online trial address, please do not create

Reconstruction of formgroup

Form component changes

In addition to icon, the biggest change in 4. X is form

  1. Instead of using the form. Create high-level component to wrap the form, we use hooks or ref to operate the form instance;
  2. In the past, the data binding of each form component was completed by getfielddecorator, but now it is completed by formitem;
  3. The initial values were previously set through getfielddecorator, and they are dynamic, that is, the value changes and the form value changes. Now it is set in the outermost layer of the form, but it is set in the form of default value, that is, the value changes, and the form value does not follow the change;
  4. The biggest changeIncremental update, version 3. X, any form item change will cause all the form items in the form. Create package to be re rendered, which is a huge performance consumption; After 4. X, if any form item changes, only the form item with the shouldupdate attribute set can perform render, similar to the new one in react 16componentShouldUpdate

According to the above change points, from the outside to the inside layer by layer analysis, targeted reconstruction;

Changes in formgroup

The former formgroup component not only collects the form method and public configuration, but also takes over the rendering layer inside the component as an identifier; In version 3. X, the form instance is provided by form. Create, that is, the business code; 4. X is similar to it, except that the form instance is generated through hooks.

The main change point is that the 4. X version of form needs to provide the setting of initialvalues, which is a default value setting. Therefore, we need to expand it to support that when values are asynchronous data, the value of form items can follow its change. The principle is very simple. Listen to the change of value and reset the form data. The implementation code is as follows:

//Pseudo code, only related changes
const FormGroup = (props, ref) => {
  const { formItemLayout = layout, children, datas = {}, ...others } = props;

  //It is compatible with the writing of non hooks component calls, and declares a ref internally for standby;
  const insideRef = useRef();
  const _ref = ref || insideRef;

  const formProps = {
    initialValues: {}, // why
    ...formItemLayout,
    ...others
  };
  //If the data value changes, reset the value of the form
  useEffect(() => {
    const [data, apiStr] = Type.isEmpty(datas) ? [undefined, 'resetFields'] : [datas, 'setFieldsValue'];
    //The function component adopts form operation;
    if (props.form) {
      props.form[apiStr](data);
      return;
    }
    //If it is a class component, the ref example is used to update the component
    if (typeof _ref === 'object') {
      _ref.current[apiStr](data);
    }
  }, [datas]);


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

There’s a code on it initialValues: {},It will make people confused, why not assign the value to data; This is another hidden knowledge point of antd

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

//I want to clear the back
form.setFieldsValue({}); 

//Finally found that the above empty did not take effect, the reason can think for yourself

So when we want to do some requirements, for example, edit a form first, and there is data in the form; But no operation was done. After closing, you click the Add button and pass an empty object. The bug is found. The last edited data is still available. In addition to this, there are other business scenarios that will be used. There is a similar issue in antd:

How to clear the form simply when there are many elements in the form with initialvalue

So in this component design, we willinitialValuesThe default setting is null data, and then the set data is usedsetFieldsValueTo reset. If you want to clear the form, you can directly pass in an empty data. After it is detected by the component, you can call it internallyresetFieldsTo achieve. Praise me for my talent.

Changes in formrender

Compared with the change of formgroup, the sub component formrender has less change. It mainly adapts to the second change and is more intuitive in the way of code

// 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>);

After reconstruction

// 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>);

The realization and change of linkage form

It mainly realizes three kinds of linkage, changing the associated form items according to the changes of other form items

  • Whether to render or change the rendering mode;
  • Whether it is disabled or not;
  • Verification rules

In the past, each change of form item will cause other form item renders, so it can be implemented by adding useref and Bao to onchange of each form item in formgroup violently;

The new form adds the onformchange callback to support incremental data collection, but the fourth change makes the old scheme GG; The linkage of form items needs to rely on shouldoupdate, which is also the officially recommended scheme;

Component library refactoring, support antd 4. X

The essence of this method is that the formitem with the shouldupdate attribute is only used as a container. This container listens to events like onformchange, and then determines whether to re render the sub elements in the container according to shouldupdate. The rendering implementation of sub elements is a design pattern that applies react renderprops;

So the linkage scheme seems to be simpler. Let’s add a layer of formitem package and see the implementation of some codes

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 >): / * non linkage implementation*/

other

In addition to the above changes, there are many other changes, such as:

  • In fact, the search box component also relies on the formgroup, so some minor adjustments have been made internally, but the business code does not need to be changed at all;
  • In the past, rangepicker’s data auto assembly was supported. However, due to 4. X’s support for switching between dayjs and moment time libraries and the advance setting of initvalues, this function has been temporarily cancelled;
  • Loading style files on demand;

Also, there are some new features that have not been considered.

Use comparison

Implement a small edit box, similar to the following:

Component library refactoring, support antd 4. X

Code before refactoring

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);

After reconstruction

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

If you don’t look carefully, is it hard to detect the change

Reconstructing feelings

Because the 3. X version is not as skillful as it is, and many features are not yet used, we need to refactor a simple version first to complete the daily scene, and then iterate slowly.

If you are interested, you can fork the project or view the project documentation

Original address of the article