1 the concept

1.1 What is a form

In fact, forms in the broad sense are not particularly well defined. Wikipedia says a form is a series of documents with Spaces for typing or selection. More specifically, forms on web pages are mainly responsible for data collection functions, and all forms mentioned below refer to the latter. The following image shows the form for configuring the page to change the name in Google Profile:

1.2 Responsibilities of the Form

Forms convert user input into specific data structures through appropriate UI & interaction, usually objects in JS and usually JSON in transit, such as the most likely actual form data in the example above.

{lastName: ' ', firstName: ' ',}Copy the code

More specifically, forms address the following common issues:

  1. UI components corresponding to specific form items, such as input boxes, drop-down boxes and so on;
  2. Organize around UI components, the style of their corresponding labels and the overall layout of error messages;
  3. Form verification actually includes simple verification and dependent verification.
  4. Representations of nested data, such as lists & objects;
  5. Linkage between fields.

2 React Official form scheme

The React form solution is as simple as reactjs.org/docs/forms. . In general, there are officially two different forms implementations, one based on controlled components and the other based on uncontrolled components, although the former is more common. All third-party forms can be considered extensions and encapsulation of these two solutions.

2.1 Controlled Components

In simple terms, it means that the parent component completely controls the state and callback function of the component. The state change of the child component needs to be notified to the parent component through the return function, and the parent component completes the state change and then sends the new value back to the child component.

This is expressed in code as a form component that accepts both value and onChange props to implement control. Here is an official example:

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ' '};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>); }}Copy the code

2.1 Uncontrolled Components

A controlled component has all its state taken over by the outside world. An uncontrolled component, on the other hand, stores its state internally. We can access it using the Ref in React. Again, the official example:

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.input = React.createRef();
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.input.current.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" ref={this.input} />
        </label>
        <input type="submit" value="Submit" />
      </form>); }}Copy the code

2.3 Which to use

I’ve done a lot of research on controlled vs. uncontrolled choices, and most of the documentation suggests that controlled should be a priority, but I still can’t find a compelling reason. A lot of documentation is nothing more than enumerating controlled than uncontrolled, more flexible, more React single data streams, and it feels more like looking at this stuff for political correctness than based on a real development experience. There’s even a widely circulated comparison:

In fact, ref and onChange can be used to cover some scenarios that cannot be covered by uncontrolled components listed below. For example, real-time verification at field level can be achieved by attaching a listening function to a field and verifying the field when its value changes. The internal state of the component is then controlled through ref. So I don’t think the above scenario is a good reason not to advocate uncontrolled components.

We’ve talked about this before, and here’s an interesting one:

The real problem isn’t react, but the idea behind the React implementation is the ViewModel. Of course, the React Core Team wants things developed using React to be controlled by the viewModel as well.

However, my personal understanding is that controlled and uncontrolled are based on the storage location of the component state (value), or whether the component is a so-called Dummy Compnent. Essentially, controlled and uncontrolled expression capabilities are the same and can be mutually realized on some level.

3 React Community Form solution

Although the official scheme is simple and intuitive, it is still a little simple to write requirements directly. The scene is a little complicated, but it is not very efficient. Naturally, the React community comes up with quite a few three-way forms. Here are a few typical ones. Note that since many forms are designed to be interlinked, I will only pick one form for some of the specifications/implementations (such as support for nested data).

3.0 Pre-Concepts

Before diving into the various form scenarios, I want to add a front-loaded concept called Field. What is Field?

The concept of a Field is broad and can be understood simply as an abstraction of a form component such as an input Field. To discuss this problem in more detail, we can start from the beginning. How do we write a form in React?

  1. From a controlled perspective, first we define a state to manage the state of the form, and then we attach value+onChange to each form component.

  1. Next, we might want to add a validation rule definition for the form item, add a title, declare name, and map the form item to the underlying data structure. That’s what Field does. Therefore, Field mainly helps us to solve the state binding and verification binding of specific form components, as well as a series of general logic such as label setting and even style, error information display and so on.

  2. The form. Item in the familiar ANTDesign Form can be interpreted as a Field.

As you will see later, almost all class libraries contain this concept. In essence, you can think of each Field as encapsulating everything about the input box it contains.

3.1 rc-form (Antd Form 3.x )

3.1.1 background

This Form is actually the underlying dependency of the familiar component library Antd Design Form 3.x, and essentially anyone who has used Antd Design Form can assume that they have used this Form. The decision to start with the form was based on that.

Of course, the form itself is not complicated and has distinctive features. Both the source code and the exposed API have a generational feel, which can be seen as a product of the Class Component era.

3.1.2 example

Take a quick look at the official example and get a feel for it:

import{Form, Icon, Input, Button}from 'antd';

function hasErrors(fieldsError) {
  return Object.keys(fieldsError).some(field= > fieldsError[field]);
}

class HorizontalLoginForm extends React.Component {
  componentDidMount() {
    // To disable submit button at the beginning.
    this.props.form.validateFields();
  }

  handleSubmit = e= > {
    e.preventDefault();
    this.props.form.validateFields((Err, values) = > {
      if(! err) {console.log('Received values of form: ', values); }}); };render() {
    const{getFieldDecorator, getFieldsError, getFieldError, isFieldTouched} =this.props.form;

    // Only show error after a field is touched.
    const usernameError = isFieldTouched('username') && getFieldError('username');
    const passwordError = isFieldTouched('password') && getFieldError('password');
    return (
      <Form layout="inline" onSubmit={this.handleSubmit}>
        <Form.Item validateStatus={usernameError ? 'error' :"'}help={usernameError| | '} >{getFieldDecorator('username', {rules: [{required: true, message: 'Please input your username!'}],})(<Input
              prefix={<Icon type="user" style={{ color: 'rgba(0.0.0.25.) '}} / >Placeholder =" placeholder "/>,) {placeholder=" placeholder";</Form.Item>
        <Form.Item validateStatus={passwordError ? 'error' :"'}help={passwordError| | '} >{getFieldDecorator('password', {rules: [{required: true, message: 'Please input your password!'}],})(<Input
              prefix={<Icon type="lock" style={{ color: 'rgba(0.0.0.25.) '}} / >Placeholder =" placeholder "; placeholder=" placeholder ";</Form.Item>
        <Form.Item>
          <Button type="primary" htmlType="submit" disabled={hasErrors(getFieldsError())}>
            Log in
          </Button>
        </Form.Item>
      </Form>); }}const WrappedHorizontalLoginForm = Form.create({ name: 'horizontal_login' })(HorizontalLoginForm);

ReactDOM.render(<WrappedHorizontalLoginForm />, mountNode);Copy the code

The corresponding page is shown as follows:

3.1.3 Test run source code

Simply clone the source code, NVM switch to 10.x, yarn then YARN start can, its own project dev server, then try to modify the source code can immediately take effect.

Source code reading process, there are some less commonly used small knowledge, I hope to help you read the source code:

Mixin

React provides a common approach to reuse logic based on the createReactClass. However, it is not supported in ES6 class. The purpose is to reuse common methods that are independent of specific component classes.

hoist-non-react-statics

Used to copy static methods in the React Class Component. When using Hoc wrapped scenarios, it’s hard to know which methods are provided by React and which are user-defined. This method makes it easy to copy the non-React static methods of wrapped components onto exposed Hoc (see link). Even static methods on a component class wrapped in HOC are not lost.

3.1.4 General idea

This is a picture I left when I read the source code, I hope to help you.

3.1.5 Overall design

In fact, the overall design of the RC-Form feels relatively simple. From the point of view of the official controlled component, getDecorator is essentially HOC, internally injecting value & onChange into the form control using the Redo #cloneElement. In this way, all form controls are taken over by the RC-Form, and the subsequent interaction, whether input or output, with validated error status information is hosted internally in the component, freeing the user.

The catch is that rC-Form uses a forceUpdate component to synchronize internal state with the view UI. This forceUpdate is placed in the form component. In other words, Any small change (such as the entry of a single field) will result in the rendering of the entire form level (we like to call it global rendering), and this is the root cause of the performance problems that rC-Form has been widely criticized for, due to its rough rendering granularity.

3.2 rc-field-form (Antd Form 4.x)

3.2.1 background

Antd 4.x redesigned the form module based on some of the flaws mentioned above. More specifically, in terms of design, the control of rendering granularity is changed from form level to component level, which greatly improves form performance. React Hooks are used extensively in the source code, simplifying exposed apis and improving ease of use.

3.2.2 example

In contrast to the code examples above, the most obvious change is that the removal of the getFieldDecorator does feel cleaner overall.

import{Form, Input, Button, Checkbox}from 'antd';

const layout = {
  labelCol: { span: 8},wrapperCol: { span: 16}};const tailLayout = {
  wrapperCol: { offset: 8The span:16}};const Demo = () = > {
  const onFinish = values= > {
    console.log('Success:', values); };const onFinishFailed = errorInfo= > {
    console.log('Failed:', errorInfo); };return (
    <Form
      {. layout}
      name="basic"
      initialValues={{ remember: true }}
      onFinish={onFinish}
      onFinishFailed={onFinishFailed}
    >
      <Form.Item
        label="Username"
        name="username"
        rules={[{ required: true.message: 'Please input your username!' }]}
      >
        <Input />
      </Form.Item>

      <Form.Item
        label="Password"
        name="password"
        rules={[{ required: true.message: 'Please input your password!' }]}
      >
        <Input.Password />
      </Form.Item>

      <Form.Item {. tailLayout} name="remember" valuePropName="checked">
        <Checkbox>Remember me</Checkbox>
      </Form.Item>

      <Form.Item {. tailLayout} >
        <Button type="primary" htmlType="submit">
          Submit
        </Button>
      </Form.Item>
    </Form>
  );
};

ReactDOM.render(<Demo />, mountNode);Copy the code

3.2.3 Test run source code

Yarn Start starts dev server successfully after installing dev server, and then try to modify the source code to see if it can take effect immediately. Here’s a rough flow chart I compiled at the time:

  1. The overall code is fairly legible, but the idea here is pretty simple. We know that forcing rerender essentially rerender passes the latest props/state back down the component tree, which is essentially a way of communicating. However, this method of communication comes at the cost of causing all components to be re-rendered.

  2. Since mindless rerender on forms can cause performance problems, the solution must be to minimize rerender’s scope. How do you synchronize the latest Form state only to the Form item that needs to be synchronized? Naturally, the subscription pattern is used here.

  3. Based on the above design, the expansion is actually two points:

  • Each form item maintains its own state change, and value-onchange is actually circulated only on the current form item.
  • When the flow notifies other form items, how do other form items know they care about the current changing form item? Field-form introduces dependencies, shouldUpdate to help users easily declare form items they depend on.

3.2.4 Support for nested data structures

One important thing I left out of rC-Form is support for nested data structures, as we know that the user filling out the form actually ends up with a large JSON. For simpler scenarios, the form might have only one level of JSON.

For example, in the login scenario, the more common structure is:

{
   phoneNumber: '110'./ / cell phone
   captcha: 'YxZd'// Image verification code
   verificationCode: '2471'.// SMS verification code
}
Copy the code

But in fact, in complex scenarios, it is likely to look something like:

{
   companyName: 'ALBB'.location: 'London'.business: {
      commerce: {
        income: {
          curYear: 110.lastYear: 90,}},data: {
         dau: 1,}},staffList: [{name: 'zhang3'.age: '22'
     },
     {
        name: 'li3'.age: '24']}},Copy the code

In this case, a simple <FormItem name=”field1″<Input /><FormItem> may not be enough. For example, in this case, we need form items. The form contains nested structures of objects and lists, and we need to express what we call nested relationships at the form level. In general, forms support nested data through the unique identifier name of the form item. Take field-form as an example:

import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import{Form, Input, InputNumber, Button}from "antd";

const Demo = () = > {
  const onFinish = (values) = > {
    console.log(values);
  };

  return (
    <Form name="nest-messages" onFinish={onFinish}>
      <Form.Item
        name={["user","name"]}
        label="Name"
        rules={[{ required: true }]}
      >
        <Input />
      </Form.Item>
      <Form.Item
        name={["user","email"]}
        label="Email"
        rules={[{ type: "email"}}] >
        <Input />
      </Form.Item>
      <Form.Item
        name={["user","age"]}
        label="Age"
        rules={[{ type: "number",min: 0.max: 99 }]}
      >
        <InputNumber />
      </Form.Item>
      <Form.Item name={["list",0]} label="address1">
        <Input />
      </Form.Item>
      <Form.Item name={["list",1]} label="address2">
        <Input.TextArea />
      </Form.Item>
      <Form.Item>
        <Button type="primary" htmlType="submit">
          Submit
        </Button>
      </Form.Item>
    </Form>
  );
};

ReactDOM.render(<Demo />.document.getElementById("container"));
Copy the code

You can see that the nested information of the data field is essentially flat stored in the name (in this case, the name is an array). Of course, a more common way to do this is to use a path rule like lodash get/set: User.name user.age address[0]; There is also an official explanation as to why ANTd 4.x uses arrays rather than the more common practice:

In rc-form, we support like user.name to be a name and convert value to { user: { name: ‘Bamboo’ } }. This makes ‘.’ always be the route of variable, this makes developer have to do additional work if name is real contains a point like app.config.start to be app_config_start and parse back to point when submit.

Field Form will only trade [‘user’, ‘name’] to be {user: {name: ‘Bamboo’}}, and user.name to be {[‘user.name’]: ‘Bamboo’ }.

As you can see, this is actually done inside the form as well, so all fields, regardless of their level, are managed flat, and although you can essentially think of it as a tree, the information about the structure is just the name. For example, if we print the above example, it essentially says:

Antd 4.x form API NamePath

All you do with a form item is you take a NamePath and then you operate on the corresponding match. To put this in another dimension, you essentially pass in a NamePath that is the path to the node that you want to operate on. The slight caveat here is that when the target node is a non-leaf node, updates need to be synchronized to all of its descendants.

Valueutil. ts, Field. Ts #onStoreChange.

3.2.5 Dynamically adding and subtracting arrays

Now that we have nested data, let’s look at another problem. A lot of times we don’t know in advance how many items there are in an array on a form. For example, when a user enters his hobbies, he can have as many hobbies as he wants.

While ANTD 4.x provides a form. List component to help us easily build forms that dynamically add and subtract arrays, the implementation doesn’t get around the nested data structures we mentioned earlier. Here’s an official example:

import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import{Form, Input, Button, Space}from "antd";
import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";

const Demo = () = > {
  return (
    <Form>
      <Form.List name="sights">{(fields, {add, remove}) => (<>
            {fields.map((field) => (
              <Space key={field.key} align="baseline">
                <Form.Item
                  {. field}
                  label="Price"
                  name={[field name, "price"]}
                  fieldKey={[field fieldKey, "price"]}
                  rules={[{ required: true.message: "Missing price"}}] >
                  <Input />
                </Form.Item>

                <MinusCircleOutlined onClick={()= > remove(field.name)} />
              </Space>
            ))}

            <Form.Item>
              <Button
                type="dashed"
                onClick={()= > add()}
                block
                icon={<PlusOutlined />}
              >
                Add sights
              </Button>
            </Form.Item>
          </>
        )}
      </Form.List>
      <Form.Item>
        <Button type="primary" htmlType="submit">
          Submit
        </Button>
      </Form.Item>
    </Form>
  );
};

ReactDOM.render(<Demo />.document.getElementById("container"));
Copy the code

The actual rendered page looks like this:

Rc-field-form #src# list.tsx. Essentially, it does three things:

  1. It simply maintains an array of increments and subtractions for the user. Each object in the array corresponds to a node in the form array.
  2. When the user wants to add an item, a new field is created in the array, and a unique key is given. The name is generated based on the position of the item, and onChange is called to inform the form object to update its internal state.
  3. The same applies to delete and add.

Question: What are the three properties of each field object in ANTD: fieldKey, key, and name?

Name is the best to understand, which corresponds to the position of each input box in the data structure submitted in the end. Key represents the unique ID of the node (which can be used as the key in react Loop). FieldKey has always puzzled me. Because field is not actually included in the field-form source code, it is not specifically explained in the ANTD documentation. But it turns out that fieldKey and key are essentially the same thing, because in antd#components#Form#FormList I found:

3.2.6 feeling

By comparing Antd 3.x and Antd 4.x, we can draw an interesting conclusion: the performance problem of the form solution is to solve the communication problem between the form items and the form as a whole. Here antD 4.x uses subscription to selectively notify instead of mindless Rerender stream redraw. Essentially, finer grained component communication achieves smaller communication cost. Next, we will look at the development of several similar forms abroad, which also confirms this point of view.

3.3 ——- International division ——–

In fact, domestic communities are mainly antD Form, but in fact, foreign React community forms have similar development ideas. The following is roughly over, the idea of similar places will not expand details.

3.4 Redux – Form

Redux-form is an early react form solution, and due to the popularity of Redux, it was a natural idea to use Redux for form state management. The form before V6 looks something like this:

There are two problems:

  1. Performance issues: Any keystroke by the user will cause a state update and render the entire form globally, which is similar to the problem we discussed with RC-Form.
  2. Reliance on REDUx: Using ReDUx to manage the flow of form data proved unnecessary, and overall increased volume, resulting in the mandatory installation of ReDUx in many projects that did not need reDUx. Check out Dan on this one.

If you’re still on the fence, check this out:

Should I put form state or other UI state in my store?

Redux-form Redux-Form

In fact, redux-form changed its update policy after V6 to register fields separately with Redux, using Redux’s natural subscription mechanism to update fields directly. It can be considered that the optimization of RC-Form is similar to that of RC-field-form.

3.5 Formik

3.5.1 track of background

Given the problems with Redux-Form’s heavy reliance on Redux, Formik abandoned Redux and maintained a form state internally. I looked at the author’s design motivation, which was to reduce template code & take into account form performance. Although FastField is provided for some performance optimization, it still provides coarse-grained status updates from the perspective of the form as a whole, so there is no escaping global rendering per se. Apart from FastField, it is even similar in design to RC-Field.

3.5.2 example

3.5.3 Core Idea

The so-called FastField performance optimization is simply wrapping the actual Field with a layer of HOC and using shouldComponentUpdate in the middle layer to determine if the current update state is the Field state of the HOC package.

If field A determines whether field B is an input field or a dropdown field, the value, error, and prop lengths passed in the form will be compared to the isSubmit key fields. You have to implement shouldUpdate logic yourself. So the overall picture is this:

3.5.4 React DevTools Tips

When exploring this form, I came across an interesting conclusion here. Since it is only FastField that connects to the context and shouldComponentUpdate that optimizes the inner layer, In this case, devTools does not allow you to see if highlight Updates are global or not, which can be misleading in this case. A better way to do this is to use the profiler provided by the plugin. This example illustrates this point:

Demo address: codesandbox.io/s/ 7vHSw

2.6.2 feeling

All in all, Formik has achieved its original design, freed itself from Redux, mitigated some of the sexual issues associated with global form rendering, and has a small package size of 12.7K, which is great in an era of Redux-form (no wonder it was officially recommended). However, due to the early release, the support for subsequent React hooks is not very comprehensive, and the underlying design does not support finer spatial update granularity, so when there are many form bloat or linkage scenarios, FastField alone is not enough to solve the problem.

3.6 the React – final – form

This is the latest effort by the redux-Form authors after years of maintaining redux-Form as a “pure JS, plugin-free” form. Final-form takes a similar approach to RC-Field-form rendering, with the form acting as a central subscriber responsible for the distribution of component events, while each field manages its own data, independently subscribles to other form items it is interested in, and performs rendering independently.

Final-form: Next Generation Forms with React Final Form

3.7 Changes of the above five form schemes

In fact, you will find that the history is surprisingly similar. In China, the subscription-based RC-Field-form was launched due to the troubles of rC-Form global rendering, and the development of foreign countries is similar: Redux-form (v5) => redux-form(v6), formik => React-final-form. To summarize, the whole transition can be understood this way:

3.8 the React – hook – form

3.8.1 background

React’s controlled forms mode is the same as react’s controlled forms mode, which maintains a state for all forms, whether they are updated centrally or distributed by subscription.

Now, we might as well change a train of thought, from the Angle of uncontrolled, uncontrolled form, it is not use state that a set of, it is directly by ref to form components, which can directly get the value of the form does not need to form the value of status maintenance, this makes the uncontrolled form can reduce a lot of unnecessary rendering.

The react-hook-form library was created after the react-hooks were react-hooks. The react-hook-form library was react-hooks. The react-hooks library was react-hooks. Hugging is uncontrolled. The idea is that each component maintains its own REFs, which are used to retrieve form item values when validation, submission, and so on occur.

3.8.2 Simple Example

3.8.3 Core Idea

Source Commit: 1441 a0186b8eab5dccb8d85fddb129d6938b994e

IO /s/ happy-mcC…

There are actually a few issues here, since all rerender are field level and not form level (form rendering), i.e. :

  1. Performance optimization: Since the individual field states are hosted by the component itself, there is no need for data backflow. Besides this big chunk, there are many details in the code that are handled well:

    A. Perform a shallow comparison of errors. For example, if the error message A was displayed in the previous render, do not re-render if the error message remains unchanged in this render.

    B. The internal states of the form (isDirty, touched, submitCount, ISbiographies, etc.) are all in a Proxy package. In the initial rendering, a Proxy is used to record the user’s subscription to each state. Does not cause re-rendering.

    C. Although Watch can trigger global rendering by default, useWatch can notify the update of a field without triggering global rendering, which is essentially a subscription mechanism and maintains the state hook of useWatch caller on the internal object of the form. This way you can only notify the subscription component of any updates.

  2. Unfortunately, any change in the error message under the form triggers a global rendering, which doesn’t feel particularly good. At least you can do a shallow comparison in ErrorMessage.

3.8.4 Dynamic Verification and Linkage

To support dynamic verification, the react-hook-form also attaches onChange, onBlur and other events to the form component during form registration to ensure the monitoring of user input and modification behaviors, so that form verification and form value monitoring can be triggered.

In addition to the problem of dynamic verification, the uncontrolled form also has the problem of linkage. Since react-hook-form does not maintain form values in state and user input does not trigger JSX updates for the entire surface layer, react-hook-Form provides Watch and useWatch with better performance. To register forms that need to be linked, and to call updates when the user makes changes. This is essentially similar to the purpose of the RC-field-form dependences/shouldUpdate.

About watch and useWatch

In fact, there are performance differences between the two. UseWatch can be considered as moving the update to a more local location, so it has more advantages in performance:

3.8.5 Compatible with three-party UI libraries

For example, Antd#Input is not a ref. The Controller component is also provided. The third party component packaged with this component only needs to follow the default onChange/value controlled specification. The Controller builds its internal state, mimicking the Uncontrolled Component.

3.8.6 feeling

In the end, react-hook-Form is similar to other controlled form libraries. They both hope to manage state distribution. Updates of single form items do not affect other form items. This is even more natural with an uncontrolled implementation called React-hook-form. As you can see, uncontrolled can do just about anything a controlled form can do, which is why I personally mentioned in Section 2.3 that controlled and uncontrolled can, on some level, be mutually realized.

3.9 Horizontal Comparison

Here is another horizontal comparison:

4. Formily (1.x)

4.1 background

If you don’t know Formily before, the following content may be a little strange. It is suggested that you can have a general understanding of it. Because I’m not going to talk about the basic uses of Formily in this section, I’m just going to talk about my understanding of Formily.

The reason why Formily is singled out as a lecture is that at this stage of my form research work (October 2020.10), it is no exaggeration to say that Formily is a form solution with the most advanced (or radical) design concept and the most thorough research in the field of forms within my cognitive range at that time. At the time of writing the summary, the new version 2.x has been released as a preview, which reportedly has a lot of improvements, but I haven’t had time to look at it carefully, so the following is for 1.x only.

4.2 scenario

In my cognitive, although Formily purpose is large, for the preset considered relatively complete scenario, but I feel it is suitable for high complexity, form linkage, have extreme performance requirements of the form of a scene, if just simple scenario does not necessary, as we say, antd 4. X has come on all the volume rendering, If you use dependencies/shouldUpdate properly, the performance is good enough. However, if your scene is very complex, especially if there is a lot of linkage logic, formily can help you to reduce the logic and mental burden.

4.3 Learning Cost

Formily, as I mentioned above, has a very good idea, but the industry is divided, with the cost of learning being one of the biggest complaints. I personally think this is the core problem that has kept Formily from taking off (at the time I wrote this, Formily had over 3000 stars). Frankly speaking, Formily’s learning cost is relatively high compared to other forms solutions in the community, but there are two main reasons:

  1. User documentation:

    A. In the Formily user group, it is common to see some users complain that the Formily official documents are slow to access and even cannot be opened, which IS something THAT I personally encounter from time to time.

    B. The user documentation is not clear enough, many areas are not clearly described, many details are not mentioned, you need to explore this point, antD Design is the industry model (this is not absolute, after all, Formily is a high-level class library in the form vertical domain. Antd takes the component library as the core, and the cognitive threshold of the two is also different.)

  2. The whole scheme is large and comprehensive with advanced concepts, which also leads to many new concepts, such as Schema description structure, CoolPath path system, Effects and even the introduction of RXJS for linkage management. Although a careful look will find that all the concepts are reasonable to some extent, for most developers, As low a learning cost as possible and as efficient as possible, Formily’s many elegant concepts are unfriendly to new users.

4.4 concept

It said that Formily cost so much to learn, but I still hope to talk about it, and even opened a separate section to talk about Formily. Because its design concept really represents a relatively advanced level of forms in the industry. Here are a few things that stand out to me:

4.4.1 communication

4.4.1.1 From Data Back to Subscriptions to Effects

Looking at the forms in the industry one by one, I have a very intuitive feeling that each form solution, in the final analysis, is actually to solve the communication problem between various form items and form items and form as a whole:

  1. We can see the redux form (< 6) and RC-form at the beginning. They informed the information to each Field through the new props layer by layer rerender after full rerender. In this way, the communication efficiency of information was very low and the form performance was poor.

  2. The rC-field-form and other forms have adopted a separate, project-dependent subscription update approach, which essentially leads to better communication efficiency and better form performance.

  3. In addition, without exception, they all advocate the combination of onChange + fomrRef to express logic:

Naturally, Formily is subscription-based, too, but it’s more unique in that it converges all of your forms-related logic into a field called Effects, written in a novel way:

The core here is $(‘event_type’, ‘path_rule’), which means search for all the form items path_rule hits, subscribe to their event_type event, and the overall return is an RXJS stream. Here I would like to say a few things:

  1. First of all, effects converges the behavior of various forms and linkage logic. This is a great innovation, compared to other solutions where onChange callbacks are hung on components scattered throughout the view layer. This convergence really helps improve the readability of the code.

  2. Path_rule must follow the author’s own implementation of a DSL, cool-path, which feels like a good idea. The goal is to be able to pinpoint a specific form item, but also to be able to batch process and subscribe based on the situation. In fact, it can also be regarded as a big innovation, it endods Formily with strong linkage expression ability, one-to-one, one-to-many and many-to-many can be well expressed. However, the bad thing is that the document is not complete, but its syntax is a little different, neither path system nor glob pattern. In fact, to tell the truth, I have used it for a long time and sometimes I still don’t understand it.

  3. The introduction of RXJS here is certainly geeky, but from my point of view it feels like all the scenarios are being used as simple event feeds.

4.4.1.2 about react – Eva

Effects + Actions + RXJS is a combination of effects + Actions + RXJS and a library named React-EVA. The idea is very clear, that is, RXJS implements internal and external communication solutions for the React component.

This scheme has two core advantages:

  1. Because of linkage performance, following this specification automatically eliminates the need for hooks in the view layer.
  2. Improved code maintainability, with all logic folded into Effects.

4.4.1.3 Talk about core understanding from “Effect is executed once”

Before, a colleague asked me this question: why does my React Hook still have the old value in an observer of the latest effect even though it has been updated?

In fact, if you do a few more experiments, you’ll see that the Effects function itself only executes once. This is superficial, but I understand that the deeper reason is that the author does not want effects(think of the linkage logic you declare) to be dependent on hooks in the current view layer for two reasons:

  1. Effects should be something pure like reducer, and can even be extracted and reused.
  2. On the other hand, if effects is coupled to view-level hooks, it means that every time you need effects to redo, you need setState, which is automatically redraw the entire table, which is a performance backslide that the author doesn’t want.

So Formily in my view isn’t really React, it’s more like an automata, you’re writing JSX/Schema to declare the structure of the form, and once you’ve rendered it for the first time, The form is completely closed loop with user interaction + developer defined effects. Its life cycle is more self-contained than belonging to the React component container that wraps it. This is also demonstrated in “Formily’s data model is a complete representation of the form UI.”

4.4.2 Underlying data model

4.4.2.1 Subscriptible model as base class

I just said that all events in the form can be declared and listened to through effects. In fact, this is still true inside the form. It can be said that Formily, both inside and outside, is based on a simple idea that all structures can be subscribed to, such as its class inheritance model as follows:

4.4.2.2 Form = a bunch of fields organized in a tree structure

Another thing that struck me was that Formily’s underlying data model is a complete representation of its entire form. If you look at other forms, you may see statuses such as Store and state on each form, but they contain status-related information such as valueinitialValue, Rules, dirty, and touched. Then for Formily:

Formily not only includes this, it even describes the props passed to the input component of a field, the mount state of the current form, and the display state. Here are a few things:

  1. The state of the entire form is a complete representation of the form’s UI, which means we have a lot of freedom to manipulate forms in Effects. You can even set the state of a field to control the dropdown of that field. You can just change the data and update the view. With any other form, you would probably need to write a ternary expression in JSX or have a useState hooks for the view layer.

  2. The full expression of state here means that Formily escapes the constraints of the React view framework. It is the complete kernel that you can use to completely drive any view framework, such as Vue. Other forms have more or less view-level information left over from the React UI component.

In fact, not only the specific field, but if you zoom out, you’ll see that the entire form can be represented as a tree:

If you think about it, this is quite natural, because in a scenario with nested data, the natural structure of a form is a tree. This tree is a complete representation of the form. Combined with effects, all form interactions can be closed loop inside Effects, because Formily’s data layer can fully represent the UI layer.

Returning to the problem we discussed in section 4.4.1.3, you can see that Formily’s state flow should be Redux:

It does not need to rely on any react-related hooks/callback to loop its own links.

4.4.2.3 Immer

However, the Effects state is very free to manipulate any property of the form/form item. This gives a lot of freedom, and it does improve the user experience of the library, but the workload doesn’t just disappear. For Formily’s developers, users just set it up, so they have to take care of every situation. For example, if the user simply reset rules in Effects with state, Formily would have to consider recaliasing, updating the form’s error status, and so on. So how does the author know which property we changed in the callback?

The answer is Immer. When I saw it for the first time, it was really a big call, because in my shallow understanding, the value of Immer was only “to create immutable objects easily when we reducer redux”. However, the author unexpectedly thought of using patches in Immer to record user operation records. It’s quite natural to use here, and the whole idea goes something like this:

This is really fascinating, because when it comes to comparing the changes between two objects before and after the function, the first thought is “dirty check”, which the author cleverly circumvents. But my question at the time was: would using IMmer really cost less?

I studied the principles of IMmer. In short, Immer’s high performance is based on the fact that:

The overhead of creating/copying objects is high and the overhead of referencing objects is low.

So create as few new objects as possible for better performance. In this context, immer implements Copy on Write:

Of course, this kind of performance optimization is not necessary for small forms, but Formily is a performance drain.

4.4.3 Schema 与 Low / No Code

4.4.3.1 concept

Formily also has a concept at the top called Schema. Why do we have a Schema? To better explain Schema, let’s talk about what Low/No Code is.

Low/No Code for form direction is commonly referred to as form visual construction, and product form is a variety of form generator. For Low/No Code, however, there is a more accurate definition:

A low-code development platform (LCDP) is software that provides a development environment used to create application software through graphical user interfaces and configuration instead of traditional hand-coded computer programming.

No-code development platform (NCDPs) allows programmers and non-programmers to create application software through graphical user interfaces and configuration instead of traditional computer programming.

Its advantage is also obvious, that is to lower the threshold of development, software development work is no longer limited to professional technical personnel. One form of what the industry calls front-end empowerment is to build a No/Low Code platform that offloads requirements to the non-front-end.

4.4.3.2 Products and Implementation

There are already some form generators on the market, including Formily, for example:

Almost all forms No/Low Code solutions are based on one core concept, DSL. A DSL can be thought of as a bridge between a UI view and user interaction. Generally speaking, the process of such products is like this:

  1. Users produce DSLS in some non-coded way, such as by dragging and dropping configurations on the platform;
  2. DSLS are translated into views through mapping rules.

So a DSL is a medium, which is actually a code expression of an abstract model, for example:

The user doesn’t know what select input is but he knows what a dropdown input is;

The user may not know what relative positioning is, but he knows that he wants to move the box a little further to the left;

The user may not know what is required but he will tell you that I want this field of the form to be required.

Formily extends the JSON Schema definition below, which can also be considered a DSL:

For Formily, there are three equivalent expressions at the same time, see formilyjs.org/#/0yTeT0/8M… JSON Schema, then JSX Schema, and finally pure JSX, which is closest to React. As we said earlier, Formily’s JSX layer is more like declaring a bunch of form structures than views, and that’s where the underlying logic lies. It must ensure that the form you end up with is consistent using any of the expressions.

Formily introduced Schema with a great vision — that subsequent forms could be produced by machines or configured on visual platforms. However, from the perspective of react-schema-editor, the temporary completion is relatively limited. In my own business, I don’t have a lot of experience with Schema, and to some extent, this layer actually becomes a bit of a mental burden for scenarios where configuration/machine generated forms are not a concern. I personally feel that Formily is grounded in its React layer (core layer) + Antd bridge layer.

4.4.3.3 status quo

Formily is not the first library to offer a visual solution, but most of it has not been well received. My own understanding is a few things:

  1. Most of these platforms are designed to empower non-front-end students. In fact, the configuration process (that is, the development process) is not difficult, but how to balance the relationship between product flexibility and ease of use is rare. It is easy to fall into the dilemma of “r&d does not want to use, non-technical users will not use”.
  2. On the other hand, most solutions only address the first step of business requirements, development, which is often the simplest. However, how to expand the whole link and take the whole life cycle of the product into consideration? That is, can the subsequent debug optimization and improvement under this new development form also be carried out with the participation of the r&d students? I don’t think it’s mature yet. It needs more exploration.

4.4.4 other

In fact, Formily I contact a little more is the above some concepts, other such as style layout we use a little bit less, also do not understand more people’s children. Formily’s user documentation is not very good, but Formily’s conceptual article is very good. If you want to know more about Formily, here is the Formily Zhihu column of the official team.

For Formily, objectively speaking, they are explorers in the field of forms. Although they are not perfect and have problems of one kind or another, their flaws do not obscure the flaws. From them, I see the great wisdom of Chinese developers and give me a lot of inspiration. For those of you who want to learn more about forms, I personally think Formily is a great place to learn.

5. At the end

Some thoughts on forms technology have been scattered throughout this article, and by the time it comes to the conclusion, it feels like there’s nothing left to say. In addition, the article is also subjective as a whole, and some statements may not be so accurate. If there is something wrong, please feel free to exchange and correct it.

6. Hard ads

Now hiring! We are the front-end team of Bytedance games. The team’s current business includes several game-centric platforms with tens of millions of DAU, covering multiple bytedance hosts such as Toutiao, Douyin and Watermelon video. There is also a creator service platform with monthly flow of tens of millions, which is the most important game short video distribution platform and talent realization channel for Douyin officials. The team is responsible for these project products, as well as the related operation background, advertiser service platform and the front-end research and development of efficiency tools. Business technologies include small program, H5, Node and other directions. In addition, with the rapid development of business, technical support scenes continue to be enriched.

Welcome to the portal, or directly to [email protected].


Welcome to “Byte front end ByteFE”

Resume mailing address: [email protected]