Hi, guys. Two years ago I published an article called “Improving the React. Js Development Experience with the New generation of JS templating engine NornJ”, which was my first attempt to promote the extensible templating engine NornJ.
New ideas for the Babel JSX plug-in
Less than a week after publishing that article, I looked closely at JSX-Control-Statements and stumbled upon a new idea:
Is it possible to use Babel to extract JSX tags with special information and turn them into run-time rendering functions to break through JSX’s existing syntax-extensibility capabilities?
The idea was then implemented: Babel-plugin-Nornj-in-JSX, and has continued to be applied to several practical projects within the company. For a description of how the Babel transformation works, see here.
Next generation version of NornJ
With that in mind, and after two years of off-and-on iteration in a busy business, I released a redesigned NornJ version of the JSX API this year, rewrote the documentation, and almost completely rewrote the source code in Typescript:
Github:github.com/joe-sky/nor…
IO) : Joe-sky.gize. IO /nornj
IO) : Joe-sky.github. IO /nornj
Ant Design forms solution based on Mobx and JSX syntax extensions
Our team has been using Mobx as the React state management solution since 2016, and we have been benefiting from its highly efficient and easily optimized responsive data flow development experience for several years.
Possible pain points for Mobx adaptation of Antd forms
Although the comparison between Mobx and Redux is not for this article, after several years of experience, I have summarized some possible development pain points when Mobx works with Ant Design, the most popular React component library in China, especially the form validation component:
- The Ant Design Form component recommends data access methods that don’t work well with Mobx’s responsive data flow
The Antd Form component uses getFieldsValue and setFieldsValue (official documents) to access data, which can be awkward when using Mobx for data flow management:
-
The request backend interface stores the returned Form field data in Mobx Observable data. We then need to place the data into the Form component instance using the setFieldsValue method. The Form component data is updated. However, this data update process does not use the Responsive feature of an Observable, which feels a bit wasteful with Mobx.
-
GetFieldsValue is used to get Form field values from the Form component. When taken out like this and used directly in render (or Mobx computed), the Observer of Mobx does not automatically rerender (recompute), which may be counterintuitive:
const Demo = (a)= > {
const [form] = Form.useForm();
return useObserver((a)= >( <div> <Form form={form} name="control-hooks"> <Form.Item name="note" label="Note" rules={[{ required: true }]}> <Input /> </Form.Item> <Form.Item name="gender" label="Gender" rules={[{ required: true }]}> <Select placeholder="Select a option and change input text above" allowClear> <Option value="male">male</Option> <Option value="female">female</Option> <Option value="other">other</Option> </Select> </ form.item > </Form> // When the Form value is updated, the following text does not update < I >Note: {form.getfieldValue (' Note ')}</ I > < I >Gender: {form.getFieldValue('gender')}</i> </div> )); };Copy the code
Of course, there are ways around this scenario. But no matter how we solve it, we get the sense that there are two pieces of data: Mobx state data and the form’s own data. This can be a hassle for developers accustomed to Mobx’s responsive data flow.
- Some Mobx Observable data requires toJS transformation when passed into the Ant Design Form component
This may be a drawback to wrapped data types like Mobx Observable, but a component like checkBox. Group that manually performs a toJS conversion to a normal array every time a component’s value is passed in is a bit of a hassle.
Find the Mobx environment form scheme – mobx-react-form
We can find an existing solution: Mox-React-form
It differs from Antd Form’s approach of managing data within components. Mobx-react-form hands the form data, validation status, and so on to a special structure instance of a Mobx Observable, which notifies form-related components via the JSX extended operator API. A simple example:
import React from 'react';
import { observer } from 'mobx-react';
import MobxReactForm from 'mobx-react-form';
const fields = [{
name: 'email'.label: 'Email'.placeholder: 'Insert Email'.rules: 'the required | | email string | between 5, 25'}, {name: 'password'.label: 'Password'.placeholder: 'Insert Password'.rules: 'the required | string | between 5, 25',}];const myForm = new MobxReactForm({ fields });
export default observer(({ myForm }) = > (
<form onSubmit={myForm.onSubmit}>
<label htmlFor={myForm.$('email').id} >
{myForm.$('email').label}
</label>
<input {. myForm.$('email').bind()} / >
<p>{myForm.$('email').error}</p>
<button type="submit" onClick={myForm.onSubmit}>Submit</button>
<button type="button" onClick={myForm.onClear}>Clear</button>
<button type="button" onClick={myForm.onReset}>Reset</button>
<p>{myForm.error}</p>
</form>
));
Copy the code
Mobx-react-form’s data management approach is undoubtedly more in line with mobx’s responsive data flow. Although there is no official example, it should also work with Antd Forms with some extensions. Mobx-react-form: Antd Form: Antd Form: Mobx-React-Form: Antd Form: Antd Form: Mobx-React-Form: Antd Form: Antd Form: Mobx-React-Form: Antd Form: Antd Form
- Using JSON to define the attributes of each form field is less suitable than Antd’s JSX syntax.
- Using the JSX extension operator to inform form components that the syntax might not be very readable;
- Its underlying validation component does not provide the Async-Validator used by Antd.
Form scheme based on JSX extension – mobxFormData
Referring to mobx-React-Form’s data management ideas, I developed a solution based on Async-Validator: mobxFormData, which supports Antd V3 & V4 and has good performance by taking advantage of NornJ’s existing JSX extension capability. Detailed documentation is available here.
Codesandbox example (refresh several times if it doesn’t work once)
To use preset is simple, install Preset:
npm install babel-preset-nornj-with-antd
Copy the code
With Babel:
{
"presets": [..."nornj-with-antd" // Preset is usually placed last of all preset]}Copy the code
Then it can be used directly within JSX/TSX:
import React from 'react';
import { Form, Input, Button, Checkbox } from 'antd';
import { useLocalStore, useObserver } from 'mobx-react-lite';
import 'nornj-react';
export default props => {
const { formData } = useLocalStore((a)= > (
<mobxFormData>
<mobxFieldData name="userName" required message="Please input your username!" />
<mobxFieldData name="password" required message="Please input your password!" />
<mobxFieldData name="remember" />
</mobxFormData>
));
return useObserver(() => (
<Form>
<Form.Item mobxField={formData.userName} label="Username">
<Input />
</Form.Item>
<Form.Item mobxField={formData.password} label="Password">
<Input.Password />
</Form.Item>
<Form.Item mobxField={formData.remember}>
<Checkbox>Remember me</Checkbox>
</Form.Item>
</Form>
));
};
Copy the code
As shown above, the form field data for this scenario is placed in the formData instance returned by the
tag. Similar to mobx-React-Form, formData is a flat Mobx Observable data type, which contains various form data fields and various form data operation apis. It is very convenient to use and can connect with MOBx data flow well:
export default props => {
const { formData } = useLocalStore((a)= >( <mobxFormData> <mobxFieldData name="userName" required message="Please input your username!" /> <mobxFieldData name="password" required message="Please input your password!" /> </mobxFormData> )); useEffect(() => { axios.get('/user', { params: { ID: 12345 } }) .then(function (response) { const user = response.data; formData.userName = user.userName; formData.password = user.password; }); } []); // formData manipulation apis are all on formData instances, Const onSubmit = () => formdata.validate ().then(values => {console.log(values); }) .catch(errorInfo => { console.log(errorInfo); }); return useObserver(() => ( <div> <Form> <Form.Item mobxField={formData.userName} label="Username"> <Input /> </Form.Item> <Form.Item mobxField={formData.password} label="Password"> <Input.Password /> </Form.Item> <Form.Item> <Button type="primary" onClick={onSubmit}> Submit </Button> </ form. Item> </Form> {formdata.username}</ I > < I >Password: {formdata.password}</ I > </div>); };Copy the code
-
The mobxFormData used here is a JSX extension: The react. createElement tag, converted by Babel, is not the react. createElement method. Instead, it returns a special object structure that Mobx converts to an Observable.
-
MobxField is another JSX extension: the directive that binds a formData instance to a form.item component in both directions. In the low-level implementation of mobxField instruction, different Antd form element components are automatically updated by selecting specific value attributes and event attributes through configuration, and Mobx’s toJS method has been called in this transformation, so there is no need for manual toJS.
The mobxFormData syntax looks similar to the React JSX environment, and the IDE syntax hints are complete. In addition to the syntax, its various functions are also quite comprehensive, Antd native Form can achieve almost all of it. See its documentation and examples for details.
MobxFormData’s various form sample documents
In order to better serve developers, mobxFormData solution rewrites more than 10 of the working sample documents according to the official antD V4 version, and uses Dumi to deploy in NornJ’s documentation site: mobxFormData form sample documents.
If you compare this to the antD official form sample document, you can actually see that mobxFormData usually has less code for the same functionality.
Can mobxFormData be used in a production environment
MobxFormData scheme has been used in many online projects in most of our company, so I think it can be used in production environment if you think it is beneficial to your development experience or are interested in trying it. The author will continue to update the project and welcome your feedback if any problems are found.
Some of the authors’ experiences with JSX extensions
Finally, according to the author’s practical experience, the author summarizes some feasible experience of the current JSX extension scheme, and shares it with you here:
Lesson one: JSX extensions actually support IDE code hints
In comments on several articles, I remember more than once seeing someone ask whether Babel’s JSX extension would not work with existing Eslint and IDE synthhint environments. One conclusion is that most JSX extensions actually support IDE syntax hints.
The solution is to use Typescript, as long as you have some knowledge of TS override types, defined in global.d. TS. Such as:
const Test = () => <customDiv id="test">test</customDiv>
Copy the code
Add the TS type to the customDiv tag above, as long as:
interface ICustomDiv {
id: string;
}
declare namespace JSX {
interface IntrinsicElements {
/** * customDiv tag */customDiv: ICustomDiv; }}Copy the code
For example:
const Test = () => <div customId="test">test</div>
Copy the code
TS can be written like this:
declare namespace JSX {
interface IntrinsicAttributes {
/** * customId directive */customId? :string; // Since each component may be used, it is optional so as not to affect type checking}}Copy the code
All of the preconfigured JSX extensions for the NornJ project define types this way, and the code can be seen here. Eslint, if the TS type is defined it is usually not affected, but it may use unused variables and so on, which is not difficult to handle simply by adding a configuration, as you can see here.
Lesson 2: React Using two-way data binding is not the same as using an instruction syntax
Others felt that the concept of “two-way binding” would be an anachronism in the React environment.
Bidirectional binding is understood as the binding relationship established between view components and data models, which are updated bidirectional synchronously. This scenario can also exist in React. For example, Antd’s Form component, from the early versions up to the latest VERSION V4, has always managed data in a similar way to two-way data binding in my opinion, but without using an instruction API. Bidirectional binding is always described in its official documentation.
Different Babel JSX extension projects have different implementations of instructions, most of which are syntax-sugar conversions; NornJ’s mobxBind directive, for example, implements a high-order component called React. So an API is just a formality, not necessarily an underlying implementation.
Lesson three: What are the existing JSX syntax extensions
This area is definitely out of the way. Here are a few Babel JSX extensions that the author has seen over the years that provide common JSX extensions such as process control:
- Github.com/AlexGillera…
- Github.com/peakchen90/…
- Github.com/jsx-plus/js…
- Github.com/bukharim96/…
- Github.com/aardito2/ba…
Extensible Babel JSX plug-in known to the authors:
- Github.com/joe-sky/nor…
- Github.com/leegsen7/re…