LavaC

LavaC

Still finding my answer
github

Summary of One Month of Formily Usage Experience

Background#

Due to work reasons, I switched to a completely unfamiliar tech stack to develop a new project for nearly a month. React and Storybook were manageable, as I got the hang of them in about a day, but it took me over half a month to get started with Formily. Now that a phase of work has ended, I’ll summarize these experiences.

Due to project reasons, my use of Formily may not be the best practice and might not even be correct. My usage related to forms is almost zero, with the main scenarios focused on using @formily/antd-v5 and custom components.

Basics#

Formily is an open-source framework developed by Alibaba that describes forms using Schema. However, the reason we use it in our project is not for building forms, but for dynamically constructing pages.

There are three important concepts in Formily: Form, Field, and Schema.

  • Form is the core of the entire form, and its instance can be used to get and set form validation, values, fields, field linkage, and other content.
  • Field is a component of the Form, representing fields. For example, if a form has an input box named keyword, then every time the content of the input box is modified, form.values.keyword will change accordingly.
  • Schema describes the data of the Field and can be simply considered equivalent to ReactNode. For specific contents that a Schema can include, refer to the official documentation.
    • During the process of converting Schema to components, Formily will by default proxy the component's value and onChange, so this needs to be considered when developing Formily components.

Starting with a Simple Page#

Let's start with a simple example.
Pasted image 20241003170934.png

Pasted image 20241003171026.png

The code above creates a scene of a ==“div with a border containing an input box and another container with two input boxes”==. From the code, we can draw several conclusions:

  • The hierarchical structure of Schema corresponds to the hierarchical structure in HTML, where properties serves a role similar to children.
  • Can some initial states of the form be defined in createForm?
  • x-component represents the element that needs to be rendered at the current position, supporting both native tags and custom components, with custom components needing to be registered in createSchemaField.
  • x-component-props defines the props of the component, while x-decorator defines the component that wraps this component.

By performing simple operations on the form, we can observe the mapping relationship between Schema and the actual form values.
Pasted image 20241003171319.png
image.png

If we want to manage the values of different fields, the type in the schema is very crucial.

  • When type is void, the form will ignore the path at this level.
  • When type is object, this field will become an object that holds child fields.
  • When type is a basic type, this field represents a specific value.

At that time, we originally planned to encapsulate a component that combined Card + Tabs functionality, but under Formily's mechanism, a field itself cannot represent its own value (Tabs' activeKey) and also become an object containing child fields, so we ultimately abandoned this approach.

Function Handling#

If we insist on using JSON Schema, then stuffing functions into the Schema seems less reasonable. In the Schema, Formily will process strings in {{}} as functions, so we need to adjust the syntax.

// ...
	input: {
		type: "string",
		"x-component": "Input",
		"x-component-props": {
			onClick: `{{ (event) => { console.log(event) } }}`
		}
	}
// ...

Similarly, for props that need to pass ReactNode, the same processing applies, but we need to use React.createElement.

import { createElement } from "react";
import { Input } from '@formily/antd-v5'
import { SearchOutlined } from '@ant-design/icons';

// ...
const SchemaField = createSchemaField({
	components: {
		Input
	},
	scope: {
		createElement,
		SearchOutlined
	}
})
// ...
input: {
	type: "string",
	"x-component": "Input",
	"x-component-props": {
		suffix: `{{ createElement(SearchOutlined) }}`
	}
}
// ...

Field Linkage#

This part is well explained in the official documentation, so I won't elaborate further.

Data Transmission#

At that time, our page had a linkage logic where switching the time in the top navigation bar would require all the charts below to update their data synchronously. Our solution was as follows, which may not be the best practice.
image.png

Component Development Process#

Formily components differ significantly from regular React components. If we take @formily/antd-v5 as the officially recommended practice, we cannot develop components using previous approaches.

Taking antd's Table as an example, both columns and dataSource are passed as part of the props to the component. However, in Formily, data should only be handled by the default configuration of props.value, so during development, we should ensure the conversion between rendering data and props.value. When integrating existing components, we can also use the official mapProps method for mapping.

To be precise, data can be placed anywhere to achieve functionality, but being able to easily access all values from form.values is more convenient than scattering data into the field's componentProps.

In addition, effectively using useForm, useField, and useFieldSchema can facilitate the retrieval of parent and child field content.

Some Pitfalls#

ReactNode to Schema#

Although we can pass ReactNode using {{createElement}}, for custom components, we still need to pass the component into the scope in advance, which is difficult to achieve with dynamic Schema.

My approach was to intercept within the component. I used useFieldSchema to obtain the Schema, then checked whether the corresponding property was also a Schema object. If so, I returned a RecursionField to render the Schema.

After writing the interception, it seemed fine at first glance, but I was baffled when it came to the sortIcon of antd's Table, as the rendered icon had no state change upon clicking.

I suspect this is related to Formily's rendering mechanism, where the rendering result of RecursionField is cached, and simply rerunning the function with new props is insufficient to trigger a re-render.

onChange#

This is also related to the filtering of the Table. In the original process, clicking the header for sorting would trigger the table's onChange event to obtain sorting information.

However, after trying various syntaxes, I found that this onChange could still not be triggered. After looking at the source code, I discovered that to prevent bubbling, the official implementation had overridden onChange with an empty function. Do you not use this functionality at all?

There was no business-level solution to this, so I ultimately resolved it using a patch, which was quite cumbersome.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.