Formily2.0: v2.formilyjs.org/

Source code address: github.com/alibaba/for…

Iteration plan: the official version will be released at the end of 2021. The beta/ RC version will continue to iterate for half a year, and the beta version has been released now

This project is mainly initiated by the author. Here, WE would like to thank Ali Digital Supply Chain Business Department for its attention and support to Formily project, and Song Sichen for his contribution to Formily2.0 with super high quality @Formily /vue. I also want to thank xiao Ze for his contribution to Formily2.0 of the super useful smart grid layout component FormGrid

introduce

If you haven’t used Formily yet, you can go to Formily and see how Formily solves form problems step by step. If you have used Formily, you must know Formily’s past positioning as a form solution for complex scenarios. However, the positioning of Formily2.0 has changed a lot. In a word, it is a professional solution for enterprise forms. What is professional? A major is having:

  • Leading ideas in the industry
  • Rich usage scenarios
  • Extreme detail optimization
  • Perfect documentation around the perimeter

Now Formily2.0 can pat your chest and say, “I’m professional enough!” Where does professionalism come from? We have to start with decryption.

When it comes to decryption, it is natural to analyze Formily2.0 step by step to truly understand its core value. Here, we mainly start from the following questions:

  • Why upgrade?
  • What problems have been solved?
  • What are the highlights?
  • What are the plans for the future?

Why upgrade?

The reason for the Formily2.0 upgrade, mainly compared to 1.x, has the following problems:

  • The performance is still not good enough
  • Dependencies are too complex, too large, and not stable enough
  • Package structure design is not elegant, intuitive
  • Internal design is not complete, flexible, resulting in a huge cost of answering questions
  • Because of the large number of questions, less and less hair

Formily2.0 has solved all the problems listed above, but it also brought the cost of Break Change and re-learning. So, how did it solve all the problems?

How are they solved?

Performance issues

Problem breakdown:

  • Initialization rendering of ArrayTable in big data scenarios is stuck
  • The initial rendering of too many fields is stuck

The essence of the two problems above problems because actually Formily will block type in field rendering first computed field condition, such as some linkage, don’t just need A value change control field B show hidden, for the first time rendering also needs to control field B show hidden, otherwise it is difficult to meet the business requirements.

What if instead of blocking the first rendering, we do the calculation after the field is mounted? This experience is definitely not acceptable, so the blocking calculation of the first rendering is necessary, but there is a performance problem due to the large amount of computation.

So how do you solve the computational problem?

Let’s first analyze where the large amount of calculation is mainly large:

  • Controlled rendering is supported because external control is supported, but the kernel is an uncontrolled state, and to go from controlled to uncontrolled, you need to listen for props changes, and there is a lot of dirty checking
  • The setFieldState that initializes the linkage will traverse all fields to find the target field. If there are many linkage rules, the number of initial field lookups will be very high, that is, there will be a large number of repeated field lookups
  • ArrayTable has a large amount of data, resulting in a large number of fields

The first point is easy to solve, we just need to abandon the controlled mode, and what to replace it with? In reference to Vue, we use Reactive mode to respond to external changes based on mobx-like solutions. This solves performance problems and reduces the side effects of dirty checks for props (for example, props passes an anonymous function, which is up to the user to decide whether the component should be re-rendered).

The second thing that’s not so good is that setFieldState is optimized, because you have to go through it, you have to find another way, right? Passive linkage mode is supported, and a responder model is designed specifically for passive linkage with a mobx-like solution (@formily/ Reactive). That is to say, only when a field is rendered will the response linkage be triggered. This solves the problem of searching too many fields

The third point is very difficult to solve because the number of fields is very large. For example, a 10-column, 100-row data easily has 1000 fields. Even if Reactive is used, many Observables will be created, so you have to compromise by turning the ArrayTable into a paging display. Even 100,000 rows of data can be easily rendered. For example, go to ArrayTable

conclusion

The performance optimization journey of Formily2.0 is now almost at its limit, but there is still room for optimization, depending on inspiration.

Dependency problem

Problem breakdown:

Styled – Components dependency problem

  • The overall package volume increases
  • Unable to override styles, lots of random class names
  • Large versions of various Break changes affect the stability of Formily, and it is easy to repeat packaging, and once repeated packaging occurs, the style will have problems
  • Unable to customize themes because you cannot reuse style variables from your own component library (LESS/SCSS)

So Formily2.0 decisively removes the Property-Components dependency, and follows the style architecture of the component library itself, such as ANTD, to less and fusion to SCSS

Immerjs dependency issues

  • Large version Break Change, affecting Formily stability, easy to repeat packaging
  • The Immutable mode has always been a poor choice for an Observable Form like Formily

Why is Immutable a bad idea for Formily? Because Formily doesn’t want to do dirty checks anymore, 1.x has iterated many versions of ImmerJS, starting with a lot of dirty checks, which have been optimized to this day, and some dirty checks are inevitable because it doesn’t rely on tracking mechanisms like Mobx does.

So Formily2.0 has moved decisively away from immer and implemented a responsive domain model internally based on @Formily/Reactive.

RXJS dependency issues

  • The overall package volume increases
  • There are various large versions of Rxjs Break changes, one of which will affect Formily stability, and the other is easy to repeat packaging
  • Not many apis are used in practice

So by removing RXJS from Formily2.0, does that make user writing logic more complicated?

This is because RXJS requires users to transform all states into Observable event flow patterns to get the most out of it.

But the mental stress is too much, and the reality is that our state is almost always modeled in terms of object entities rather than event flows.

So switching between these two mental models is too costly for the user to learn, and not much benefit.

On the contrary, although Reactive is also an Observable, it automatically transforms data into an Observable by means of hijacked agents. That is to say, it digests a large number of Observable calculations internally and exposes more actual business logic that users can easily understand. So this model is very business-modeling friendly, which proves that the introduction of Reactive in Formily2.0 not only solves performance problems, but also makes domain modeling easier for users.

Cool-path dependency problem

  • Independent maintenance is not convenient to read the source code, document access experience is not good
  • Independent maintenance is not convenient for scenario-specific debugging of forms

So in Formily2.0 you moved CoolPath directly to the Formily main repository and named it @formily/ Path

conclusion

With the overall dependency governance in place, Formily2.0 will be much smaller in overall size and much more controllable and stable.

Package design problem

Problem breakdown:

  • The package does not have a module export standard, and all apis are exported in the package @formily/ ANTd. There are conflicts in the API naming of the internal package, and the positioning of @formily/ ANTd and @formily/ ANTD-components has not been clearly explained. If the React component is classified according to the standard, it should be all together. If the core package + extension package is classified according to the standard, it should be separated
  • @formily/antd supports the ESM module standard, but other underlying libraries do not, which is very unfriendly to users using Snowpack/Vite
  • There is no separate json Schema protocol part as a unified layer, making it difficult to extend the Vue part
  • @formily/react-shared-components is a bit of a dud. It’s only for antD/Fusion reuse, but it’s hard to reuse if you want to expand the ecosystem of other components

The core API is exported from @formily/ React and @formily/core. It is no longer in the same package. This has two advantages:

  • Component packages are easier to locate, easier to write documentation, and easier for users to locate documentation
  • Naming conflicts between different packages are completely resolved as all apis are no longer exported from one package

The second point: esM module and UMD module are included in the construction of all packages, including all module standards in the industry

Third point: Separate the @formily/ JSON-schema package for use by other UI bridge libraries

4. Remove @formily/react-shared-components. Some redundancy can reduce system complexity

conclusion

It is important to ensure that the positioning and responsibilities of each package are defined, otherwise it will be difficult to explain whether you are writing documentation or answering questions.

Answer questions about cost

Problem breakdown:

The imperfect Schema protocol led to a surge in questions

  • Why is type object sometimes a VirtualField and sometimes a plain field? There’s actually a mapping inside, but it’s very subtle
  • Is x-props an attribute of the FormItem or an attribute of the specific field component? Because of the historical baggage, X-props can pass both component properties and FormItem properties. However, there is a conflict between the FormItem property and component properties. For example, addonAfter, who should be passed to?
  • Why does setFieldState need to be set to modify the component data sourcestate.props.enumIt’s hard to understand
  • X-linkages do not respond to more complex linkage requirements, such as calculator requirements

First, Formily2.0 defines a new Schema Type called Void, which automatically changes a field to a virtual field whenever its Type is Void. Whether there is a VirtualField registered on the front end

Second, Formily2.0 defines x-decorator/ X-decorator-props to describe the wrapper and its properties. Users can register the wrapper more easily, which solves the semantic ambiguity of x-props and property name conflicts

Formily2.0 maintains the state of the dataSource directly on the model layer, which is called the dataSource. We don’t need to understand the functions.enum.

Fourth point: The Formily2.0 kernel is designed based on @Formily/Reactive, which is similar to Mobx, so we have also defined a concept of a responder at the protocol layer called X-Reactions, which supports both active and passive (track-dependent) interactions. Protocol description The linkage capability is an order of magnitude stronger.

Imperfect model design led to a surge in questions

  • The default behavior of automatically deleting values when a field is unloaded is very, very difficult for users to understand, and this behavior leads to many unexpected hidden problems for users
  • The default value and value merge strategy is not perfect, and there are always some unexpected problems
  • For example, in a nested ArrayList scenario,1.x cannot automatically destroy and transpose the state of the child fields in the process of moving up and down. At the same time, the array transpose of 1.x will pollute the original array data and inject symbols into the array elements, which will confuse the user
  • Active linkage is very cumbersome to solve the calculator requirement
  • Effects cannot maintain local state internally, making some scenes extremely complex and cumbersome to implement

First point: The default behavior of discarding fields to remove automatically deleted values. If a value is to be deleted, the value is always subject to the user’s behavior. The value will be deleted only when the user manually controls field hiding (display === “none”) or manually sets the value to null

Second point: always use the user’s behavior. If a field has not been operated on by the user, merge in the order of assignment

Thirdly, the special field model ArrayField is defined, and the state transpose logic is moved to the method of ArrayField, so as to avoid polluting the original array data. Meanwhile, the transpose algorithm is optimized to a large extent to ensure the absolute correctness of the transpose process. The specific transpose algorithm will be discussed later.

# 3 and # 4: With the introduction of @formily/ Reactive, it’s all worked out. # 4 will be covered later.

The inelegant design of the custom component extension mechanism leads to a huge increase in questions

  • The global mechanism for registering components can easily lead to components not being found due to repeated packaging
  • Custom components that want to consume the form model and current field model state cannot be consumed using useForm and useField because they are used to create the model, not to consume the model
  • Why implement a component’s read state by relying on props. Disabled? That is, if you want to disable the state how do you do that?

The first point: abandon the global registry component mechanism, to create a factory registry (factory registration), first can get stronger type hints, second, more control, even if repeated packaging will not be a problem

Second, useForm/useField is responsible for using the form model and field model in context. Create the form model or field model without using React Hook, which makes the API clearer and easier to understand

Third point: the readPretty mode is added to the field model layer, which represents the reading state. The disabled state is determined to be disabled, which no longer expresses the meaning of the reading state. The readPretty attribute of the field model can be directly consumed when the reading state is realized

The imperfect documentation system leads to a huge increase in the number of questions answered

  • There is no search support, making it difficult for users to quickly locate documents.
  • There is no documentation for each package, and it is all massed together, making Formily difficult for users to think top-down, which makes it difficult for users to quickly locate documents.
  • Documentation is missing so badly that a lot of apis have to guess how it works

The first problem is to use dumi, an excellent document tool in the community, which can solve basically all problems related to documents well. Then, it is necessary to separate the independent documents of each package and operate with the idea that each package is an independent product, which can not only facilitate users to find documents, but also facilitate developers to maintain documents.

conclusion

To solve the problem of q&A cost, the core is to take the user as the standard, as long as the user feels uncomfortable, is bound to design is not elegant, not flexible and complete.

Hair amount problem

In order to solve the volume problem, the premise is to solve all the previous problems, and then further solve the volume problem, my solution is:

  • Upgrade from a shampoo costing tens of yuan to a high-end men’s shampoo costing hundreds of yuan — Schwarcome Peppermint Vitality Shampoo
  • Go to blind people and massage their scalps every week
  • Start taking Propecia every day
  • Tens of thousands of yuan for hair transplant

What are the highlights?

Formily2.0 has been upgraded to 1.x. If you want to understand the full capabilities of Formily, you can go to Formily

Independent responsive solutions

Formily/Reactive, which has been mentioned several times before, is a reference to Mobx at its core, but it addresses some issues that Mobx does not address in its complex domain model:

  • Mobx does not support dependency collection within actions
  • Mobx’s Observable function does not support filtering special objects like React nodes,moment, and immutable
  • Mobx’s Observable function automatically turns functions into actions
  • Mobx batch mode does not support partial batch
  • The Observer of Mobx-React – Lite does not support react concurrent rendering
  • Depth observe The listening object change failed to obtain the value path

The @Formily/Reactive wheel is designed specifically to solve complex domain models like Formily. It can replace Mobx and is much, much smaller than Mobx. And because Formily endorsement, complete, performance, stability are guaranteed, of course, this also proves Formily to kill Buddha, meet ghost to kill ghost attitude, as long as it can make Formily become better means, how to build wheels?

More elegant way of development -TS intelligent tips

Since Formily2.0 is already written in Typescript, this means that you can get a great intellection experience using any API. Here are two common use cases.

Pure JSX development mode

Whether it’s a Component property ora decorator property, the smart prompt for the second parameter always follows the component passed in for the first parameter, so the user doesn’t have to guess what the component’s property API is.

Schema Markup development mode:

Note that the type hints of the Schema Markup are implemented with the help of Typescript4.2’s Template Literal Types.

Support Vue2 / Vue3

Formily finally supports Vue and is compatible with both Vue2 and Vue3. Thank you song Sichen again.

Specific document address @formily/vue

At the same time, the VUe-related Formily extension component ecosystem is also being further built, again with the help of community power, community power is really powerful!

Effects local state

What is the Effects local state, let’s look at the Select asynchronous search case

In the useAsyncDataSource Effect Hook function, we can use @formily/reactive to define the state, and then consume it in onFieldReact. Our Effect Hook is completely self-contained, which means that a lot of complex form logic can be abstracted from Effect Hook in a very cohesive way, written in a very similar way to Vue3’s setup. Of course, this is not intended to be imitative. It is a coincidence after the introduction of Reactive, which also proves the feasibility and completeness of the Effects mode.

The Effects of context

If we abstract a lot of fine-grained Effect hooks from the effects function, then we need to pass the top-level context data from hooks, which is obviously inefficient. So Formily2.0 provides createEffectContext to help users get Context data quickly, just like React Context

Smart grid layout

Why smart Grid layout? Why not use the component library’s default grid layout?

Taking Ant Design’s Grid as an example, it has several major problems:

  • It’s too cumbersome to write, nesting Row/Col every time, and a lot of code
  • There is no quick way to achieve an equally distributed layout
  • There is no control over the maximum or minimum width of each column for elastic expansion

Formily2.0 provides the FormGrid smart grid layout component, which is based on THE CSS Grid to achieve, of course, it has some INTERNAL JS calculation, its core highlights are:

  • A grid layout can be implemented quickly by introducing a FormGrid component, which only applies to HTML elements under the FormGrid component
  • The default is equal partitioning, and for grids that need to span columns, you only need to package a layer of GridColumn components
  • Cross-column scenarios have auto-correction capabilities because the entire layout system is fully responsive and elastic
  • The ability to set a maximum or minimum width for each column is useful in form scenarios where you need to prevent form controls from becoming extremely narrow or wide in ultra-narrow or ultra-wide screen scenarios

The document address

Reactive concurrent rendering

Memory reclamation problem

Because React supports concurrent mode, Function Component is very unfriendly to Mobx Reactive solutions. StrictMode triggers functions twice for each update, and useMemo triggers functions twice. But useEffect/useLayoutEffect but will only perform a, the Reactive scheme is strongly dependent on useMemo, because want to create a persistent Reactive Tracker object with the current component binding, according to the front of this kind of chaotic execution logic, Reactive Tracker cannot properly reclaim memory.

@formily/ Reactive uses the FinalizationRegistry API to automatically reclaim memory by creating an object reference in the Function Component. It is then passed to FinalizationRegistry, which listens for when the reference is destroyed, and if it is, the Tracker created the first time useMemo is reclaimed.

Note that useEffect is triggered when the Function Component is executed for the second time. You need to release the listener Function created by FinalizationRegistry for the second rendering (to prevent the Tracker from being destroyed by error) in useEffect. The second useMemo Tracker can only be destroyed if the component is unmounted.

The source address

The parent and child components respond to the problem concurrently

Mainly to solve the following error problem

Warning: Cannot update a component (`ParentComponent`) while rendering a different component (`ChildComponent`). To locate the bad setState() call inside `ObjectField`, follow the stack trace as described in
Copy the code

This problem is so hard to solve that even Mobx doesn’t solve it, so what triggers this problem?

If the parent and child components depend on the same Reactive data and the child components modify the Reactive data during the first rendering, this will trigger error reporting.

What about @formily/ reaction-react?

The author spent a whole month to come up with the solution, the core of the solution is: Components wrapped by the Observer will trigger a counter increment of 1 each time they are rendered. UseLayoutEffect will trigger a decrement of 1 each time the component is updated. If the count is 0, no other components are being rendered. If the value is greater than 0, it indicates that other components are rendering and will be enqueued. UseLayoutEffect also checks the count when it is executed. If it is 0, it indicates that the whole is in a stable state and the update queue is executed in batches (removing queue elements as it is executed).

Note that there is a catch in using useLayoutEffect directly, because the number of times useLayoutEffect is executed is not stable, as mentioned in the previous section of memory reclamation. How to solve this problem? Here I use a very clever method, at the same time the counter increment 1 to create a timer, used to perform the counter decrement 1 and execute the update queue, useLayoutEffect clear timer, because useLayoutEffect is synchronous execution, if normal circumstances, UseLayoutEffect is executed before the timer is executed. Clearing the timer does not trigger counter recalcitations, but if useLayoutEffect is not triggered correctly, such as StrictMode, or if a component calls an invalid setState scenario, the timer is used.

The source address

Array transpose algorithm

The array transpose algorithm in 1.x can be said to have no algorithm, which is very incomplete and unsafe. Meanwhile, it is also difficult to troubleshoot problems, mainly because:

  • It marks each element by contaminating the array’s raw data, and then determines whether the array has been moved, deleted, or added based on each update
  • Based on the very unreliable timer to do node deletion, was completely helpless at that time

In Formily2.0, the first problem is that you can see exactly what the user is doing by directly defining the ArrayField manipulation method without polluting the array raw data.

Second question, here’s the key:

First, Formily2.0 abandons the FormGraph data structure of the past and stores all field models directly on the fields property of the form model, which is a pure object structure:

type FieldAddress = string
type FieldsStore = Record<FieldAddress,Field>
Copy the code

Since we know the operation type of the array, wouldn’t our state transpose just change the address of field A to the address of field B? Is so simple, so we each operation to do a full amount traversal, seeking to replace the field model, replace the address, the model does not need any changes, of course there is also a lot of details, but the overall train of thought is that, in all such ideas fundamentally solved Formily state transposed in the array has been on the spot.

The source address

What other details are optimized?

FormilyDevtools is installed by default

In 1.x, the @formily/ React layer is always used to communicate with chrome plugins. If you want to support Vue, you have to implement a separate communication. This is obviously not elegant, so Formily2.0 built plugin communication logic into the kernel, so that no matter which framework you connect to, Get an excellent Chrome debugging experience.

More sophisticated check timing control

In 1.x, the validation timing parameter triggerType is attached to the Field property, meaning that it can only batch control the validation timing of a Field. However, in most scenarios, the validation timing is set to a single validation rule. Some validation rules still want to use the default (onChange). Formily2.0 implements this feature.

See FieldValidator for documentation

The verification result supports the success status

In 1.x, Formily2.0 supports a warning type, which does not block submission, but FormItem has a warning style. However, in some cases, we may need validation logic for success status, so success is supported in Formily2.0. FormItem will also have a success style.

See FieldValidator for documentation

FormPath Supports relative path query

In 1.x, relative paths can only be used in X-LINKAGES. In this 2.0 update, we started with a relative path calculation for FormPath that no longer required a transform. In addition, each field model has a query method that can quickly query relative fields.

field.query('.aa').value() // Read the aa field of the same level directly
field.query('.. aa').value() // Read the parent aa field value
field.query('.. [+].aa') // Read cross-level adjacent AA field values
Copy the code

For details, see FormPath

conclusion

In fact, the biggest change in Formily2.0 comes from the introduction of Reacitve responsive programming mode, so that all problems become easily solved, of course, highlights and details optimization is not only mentioned these, there are a lot of need for you to explore and experience oh! Do you want to give it a try?

Q/A

Q: Why does the beta last six months?

A: Currently, the beta version is available, and the single test coverage has exceeded 90%. Such a long iteration time is mainly used to collect user feedback. If there is any bad design, it can be corrected in time to prevent users from burying holes in the official version.

Q: What’s next?

A: The next major step is the 2.0 form Designer, which is currently in development and is expected to be released in beta by the end of the year

Q: Will 1.x continue to be maintained after the official release?

A: The maintenance will continue, but no new features will be added, just bugfix. It is expected to continue until the end of 2022

Q: how compatible is Formily2.0

A: Not compatible with IE

Q: does @formily/antd support ANTD 3.0?

A: Not supported

Q: With Vue, why provide @formily/ Vue?

A: Vue is a UI framework that addresses a wider range of UI problems. Although its reactive capabilities are outstanding in forms scenarios, and are at least more convenient to write forms than the original React, we still need to do a lot of abstraction and encapsulation in more complex forms scenarios. So @Formily/Vue is designed to help you do these abstract encapsulation things, really let you develop super complex forms applications efficiently and conveniently.

Q: How is the Formily scenario related to FormRender?

Answer: Formily is the official unified form solution of the Group, and FormRender is the solution precipitated by the Feizhu team according to their own business scenarios. Different product ideas are reasonable if they exist.

recruitment

If you are very interested in the middle and background front-end technology, and want to continue to dig and break through the technical difficulties, welcome to Ali Digital Supply Chain Business Division, where there is a large enough platform for you to display your front-end magic, the author is also looking forward to working with you, this is my wechat account: Janry_kk

Nailing technology exchange group

Keep an eye on Formily group, and we will share all kinds of Formily technologies from time to time