When writing form items with ANTD on a daily basis, most people write like this:

To be honest, ANTD4 forms are much easier to use than ANTD3 forms, but in real business scenarios, there are still a lot of form.items.

Placeholder and require, for example, are similar, so can we abstract one more layer?

The answer is definitely yes. So what are some of the solutions:

  1. With the help of industry solutions such asformily,form-render. They are implemented by themselves and then adapted to different UIs such asantd,fusionYou can also extend your UI based on the core library it provides.
  2. Based on theantdItself to do packaging a large and complete Form scheme
  3. Progressively replace with a componentForm.Item.

Personally, I don’t like the solution of 2, because if you want to adapt to fusion or other UI in the future, the previous code is basically useless. Why bother?

So my personal choice is 3, which means THAT I write a Field component that can coexist with the form. Item so that the old code can be modified incrementally and the new code can be used directly.

So, how do you wrap the Field component? You can refer to the Formily API for this.

export interface FieldProps extends Omit<FormItemProps, 'children'> {
  name: string; // Field key on the back end
  label: ReactText;  // labelrequired? : boolean;// Whether it is necessary is not considered dynamicdisabled? : boolean;// Disable, again dynamic is not considered hereplaceholder? : boolean;// If it is text, 'please enter xx', xx is the label value above, otherwise please selecthidden? : boolean;// 针对那种<input type="hidden" />description? : ReactElement;// Descriptioncomponent? :'input' | 'textarea' | 'select' | 'radio' | 'custom'; // In real business, of course, there will be more, because it is progressive, so you can consider step by step encapsulationenum? :ArrayThe < {label: React.ReactNode; value: string | number | boolean }>; // For component types' select 'and' radio 'node? : ReactElement;// For component type 'custom'componentProps? : {// Component propertiesplaceholder? : string; disabled? : boolean; [key: string]: any; }; }Copy the code

LabelAlign, labelCol, wrapperCol, wrapperCol, labelAlign, wrapperCol, labelAlign, wrapperCol, labelAlign, wrapperCol, wrapperCol, labelAlign, wrapperCol, wrapperCol, labelAlign, wrapperCol, wrapperCol

import { FormContextProps, FormContext } from 'antd/lib/form/context';

<FormContext.Consumer key="label">
  {(contextProps: FormContextProps) => {
    // return ...
  }}
</FormContext.Consumer>
Copy the code

The layout of the form. Item nested Form.Item is a bit of a drag.

With the Field component, the Submit and Reset components come naturally. So the next step is to consider the configurational FormRender component.

Configuration is nothing more than JSON. But here’s a bit more to consider:

  1. How do you make it dynamic simply?
  2. If you want to do visualization in the future, what is the solution?

In the Field component designed above, I considered the visualization scheme, but it can only meet the simple scene, once complex, gg. So is there a way to extend it, and you can. Add two fields (this is a reference to code written by a previous colleague) :

  • ShouldUpdate: The ‘shouldUpdate’ in the form. Item
  • Render: the render function receives the form property, and we can render different components according to different conditions.

Render (); render (); render (); render (); render (); render (); render ();

Form-render It’s uI-Hidden. The core code is this:

// Compute the hidden value of a single expression
const calcHidden = (hiddenString, rootValue, formData) = > {
  if(! rootValue ||typeofrootValue ! = ='object') {
    return false;
  }
  Four basic operators are supported
  const operators = ['= ='.'! = '.'>'.'<'];
  try {
    const op = operators.find(op= > hiddenString.indexOf(op) > -1);
    const [key, value] = hiddenString.split(op).map(item= > item.trim());
    let left = rootValue[key];
    // feature: allows values from formData
    if (key.substring(0.9) = = ='formData.' && formData) {
      const subKey = key.substring(9);
      left = getExpressionValue(formData, subKey);
    }
    const right = parseString(value);
    return parseString(`"The ${String(left)}"${op}"The ${String(right)}"`);
  } catch (e) {
    console.error(e);
  }
  return false;
};

// Remove all window valid api
// For safety jest-* variable will throw error
export function safeEval(code) {
  let safeContextStr = ' ';
  if (typeof window! = ='undefined') {
    const windowContextAttr = Object.getOwnPropertyNames(window).filter(
      isValidVariableName
    );
    for (let i = 0, len = windowContextAttr.length; i < len; i++) {
      safeContextStr += `var ${windowContextAttr[i]}= undefined; `; }}return Function(`${safeContextStr} "use strict";  ${code}`) (); }// Replace the eval function
export const parseString = string= > safeEval(`return (${string}) `);  
Copy the code

Parse’s logic felt a little rough, but it worked, so it was copied.

So just to summarize, when we’re writing code, we need to think a little bit more about how we can make code simpler, and change it incrementally, rather than overnight.