As the business grows. The business of end B is getting heavier and heavier, leading to more and more difficult to meet the needs of the back. When people sit at the workstation, POTS come from the sky, and a small front end starts the reconstruction journey on the spot

1. Sort out the B-terminal to be reconstructed

The above is the structure diagram of the B end to be reconstructed, which is written by PHP. The JSON file is uploaded using the agreed fields, and the Controller reads the file configuration to generate a table in DB, and then directly from the Controller to the View layer. It can be very useful when dealing with simple logic or a single functional business. But the demand will only grow. More and more complex, especially when dealing with complex business, the whole Controller, as the data center, will gradually become as difficult to maintain as a spider web if it is mixed with too much redundant logic.

2. Design the reconstruction scheme

With a complete overview of the data direction and business context, the next step is to design the entire refactoring.

  • Inherit the previous business logic and render the entire page through a JSON file
  • The whole UI is upgraded because we’re refactoring with React, and we’re going to select Antd here
  • Separate the front and back ends, Mock locally, and then use Node to proxy or interact with data in a more flexible way

3. Modify the configuration file

Antd Pro scaffolding (umi.js + Antd), Router configuration, react-redux and other tools have been built for us.

{
     "Field Name": {
      "name_cfg": {
        "label": "label"."type": "select"."tips": "tips"."required": "required"."max_length": 0."val_arr": {
          "1": "Please select"."1": "The field is A"."2": "Field" B"."default": "1"
        },
         "tabhide": {
          "1": "Field A, field B"."2": "Field B, field C"."3": "Field C, field D"
        }
        "relation_select": {
          "url": "Other Business interface requests"."value": "Option id"."name": "Option name"."relation_field": "relation_field1, relation_field2"}},"sql_cfg": {
        "type": "int(11)"."length": 11."primary": 0."default": -1."auto_increment": 0}},... }Copy the code

There are dozens of fields in this JSON file, and here is a representative one. This is a Select form, val_arr is the value of the Select, relation_SELECT the interface that requests other business to fill in the val_arr to the Select, tabhide, tabhide, tabhide Hide the Value field of the form when the Value is Key, and separate multiple fields with commas. At this point, you need an extension file to process the JSON file into a format that you can easily handle

// transfrom.js 
let Arr = [];
let Data = json.field_cfg;
for (let i in Data) {
  let obj = {};
  let val_Array = [];
  let tab_hide = Data[i]['name_cfg'] ['tabhide']
  for (let index in Data[i]['name_cfg'] ['val_arr']) {
    let obj = {};
    if(index ! = ='default') {
      obj['value'] = index;
      obj['label'] = Data[i]['name_cfg'] ['val_arr'][index];
      if(tab_hide && tab_hide[index]){
        obj['tab_hide'] = tab_hide[index].split(', ');
      }
      val_Array.push(obj);
    } else {
      val_Array.map((item) = > {
        if (item.value === Data[i]['name_cfg'] ['val_arr'][index]) {
          item['default'] = true; }}); } } obj['id'] = i;
  obj['name'] = Data[i]['name_cfg'] ['label'];
  obj['type'] = Data[i]['name_cfg'] ['type'];
  obj['required'] = Data[i]['name_cfg'] ['required'];
  obj['tips'] = Data[i]['name_cfg'] ['tips'];
  obj['multiple'] = Data[i]['name_cfg'] ['multiple'];
  obj['val_arr'] = val_Array;
  obj['sql_cfg_type'] = Data[i]['sql_cfg'] ['type'];
  obj['sql_default'] = Data[i]['sql_cfg'] ['default'];
  Arr.push(obj);
}

// config.js
const config = [
      {
        id: 'Field name'.name: 'label -> Name'.type: 'select'.required: 'required'.tips: 'tips'.val_arr: [{value: '1'.label: 'Please select'.default: true }
            { value: '1'.label: 'Option A'.tab_hide: [A ' 'field.'field B']} {value: '2'.label: 'Option B'.tab_hide: ['field B'.'field C']}],sql_cfg_type: 'int(11)'.sql_default: -1,},... ]Copy the code

4. Form rendering

Antd’s forms component is very feature-rich. Get the Config and follow the documentation. React function components and Hooks make code simpler.

  const renderForm: () = > (JSX.Element | undefined=) []() = > {
    // renderSelect(); renderText(); renderNumber(); renderDatePicker();
  }
  const renderSelect: (configItem: ConfigListData) = > JSX.Element = (configItem) = >{... }const renderText: (configItem: ConfigListData) = > JSX.Element = (configItem) = >{... }const renderNumber: (configItem: ConfigListData) = > JSX.Element = (configItem) = >{... }const renderDatePicker: (configItem: ConfigListData) = > JSX.Element = (configItem) = >{... }Copy the code

5. Default fields & Hidden fields

incomponentDidMount“, which can be expressed in HooksReact.useEffect(()=>{... }, [])

The default field: sets the configuration table on the Form Settingssql_defaultCan.

Hidden fields: This involves overlapping fields. Previously, JQuery was used to simply manipulate DOM nodes to show() and hide(). JQ does have its advantages when it comes to manipulating the Dom.

The current solution looks like the following:

  const [hideListMap, setHideListMap] = useState<any> (new Map());
  configObj.forEach((item: { val_arr: { tab_hide: any; value: any; } []; sql_default:any; id: any; }) = > {
    item.val_arr.forEach((val_arr_item: { tab_hide: any; value: any; }) = > {
      if(val_arr_item.tab_hide && val_arr_item.value === item.sql_default) { hideListMap.set(item.id, val_arr_item.tab_hide); }}); }); setHideListMap(hideListMap)Copy the code

New Map() uses a tab_hide Array as a key, and a value as a tab_hide Array.

  const [hideList, setHideList] = useState<string[] > (); React.useEffect(() = > {
    let arr: string[] = []
    hideListMap.forEach((item: any, key: any) = >{ arr.push(... item); }); setHideList(arr); }, [hideListMap])const selctChange = React.useCallback((value: SelectValue, configItem: ConfigListData) = > {
    hideListMap.forEach((item: any, key: string) = > {
      if (key === configItem.id) {
        configItem.val_arr.forEach((val_arr_item: { value: SelectValue; tab_hide: any; }) = > {
          if (val_arr_item.value === value) {
            hideListMap.set(configItem.id, val_arr_item.tab_hide);
            setHideListMap(new Map(hideListMap))
          }
        })
      }
    });
  }, [hideListMap]);
  
Copy the code

React.useEffect(()=>{componentDidUpdate ()); react. useEffect(()=>{… },[n]), if the Select box changes the hideListMap, the hideListMap will automatically update the hideList. This satisfies the requirement of multi-field hiding.

  const renderForm: () = > (JSX.Element | undefined=) []() = > {
    return configObj.map((item: ConfigListData) = > {
      if (hideList && hideList.includes(item.id)) {
        return undefined
      }
      if (item.type === 'text') {
        if (item.sql_cfg_type.indexOf('int')! = = -1) {
          return renderNumber(item)
        } else {
          return renderText(item)
        }
      }
      if (item.type === 'select') {
        return renderSelect(item)
      }
      if (item.type === 'datetime') {
        return renderDatePicker(item);
      }
      return undefined; })}Copy the code

6. Select dynamic rendering values

// transfrom.js
let service = [];
let Data = json.field_cfg;
for (let i in Data) {
  let relation_select = Data[i]['name_cfg'] ['relation_select'];
  if (relation_select && relation_select['relation_field']) {
    relation_select['relation_field'] = relation_select['relation_field'].split(', '); service.push(relation_select); }}// config.ts
const SERVICE  = [
    {
      url: "Other Business interface requests".value: "Option id".name: "Option name".relation_field: ["relation_field1"."relation_field2"].method: 'get'}... ]// utils.ts
import request from 'umi-request'; / / request library
export const getSelectApi = async (url: string.method: string) = > {return request(url, {
    method,
  })
}
Copy the code

Previously the interface request was coupled to Config, whose fields became difficult to maintain if they increased to a certain number. Finally, we decided to decouple the form Config layer from the Service layer in order to distinguish the Config layer from the Service layer more intuitively and facilitate future maintenance.

Data interface parameter formats and fields are constrained. A Node layer is needed to deal with cross-domain processing of third-party interfaces and parameter unification.

import { CONFIG_OBJ, SERVICE } from './config';
const Form: React.FC = () = > {
  const [configObj, setConfigObj] = React.useState(CONFIG_OBJ);
  React.useEffect(() = >{(async() = > {for (let item of SERVICE) {
        for (let fieldItem of item.relation_field) { // Multi-field support
          insertObj[fieldItem] = await getSelectApi(item.url, item.method)
        }
      }
      for (let key in insertObj) {
        if (key === item.id) {
          item.val_arr.push(...insertObj[key].list)
        }
      }
      setConfigObj([...configObj]);
    })()
  })
  ...
}
export default Form
Copy the code

Merge the decoupled interface configuration into the form form. UseState rerenders when it detects a change in the direction of the data. At this point, the Form component is basically built.

6. The final

On further reflection, there are some optimizations here. We can add these configuration files to the B-side as well, and write Config and Service to the DB instead of uploading files. The service group of the B-end product is the personnel in the enterprise. B terminal user experience is also important, and an excellent B terminal is also one of the ways to improve efficiency and save costs. That’s the end of this article, which I hope is helpful (red item ◝)