大家好,两年前我已经公布过一篇文章《应用新一代js模板引擎NornJ晋升React.js开发体验》,第一次尝试推广我创作的可扩大模板引擎 NornJ 。过后的 NornJ 齐全基于字符串模板,在一些人看来它与 React JSX 环境仿佛人造不匹配,上手艰涩一时难以看出劣势。
Babel JSX 插件的新思路
在发表那篇文章后不到一周的工夫,我认真参考了jsx-control-statements,不盲目萌发出一个新的想法:
应用 Babel 提取含非凡信息的 JSX 标签,把它们转换为需运行时的渲染函数,是否能冲破 JSX 现有的语法扩大能力?
这个想法随后就被施行:babel-plugin-nornj-in-jsx,并持续利用于公司部门内的多个理论我的项目中。Babel转换原理形容,请看这里。
NornJ 下一代版本
有了下面的转换思路,并在忙碌业务中通过两年断断续续迭代,我在往年公布了从新设计后应用 JSX API 的 NornJ 正式版,并重写了文档,源码也用 Typescript 简直齐全重写:
github:https://github.com/joe-sky/nornj
文档(gitee.io):https://joe-sky.gitee.io/nornj
文档(github.io):https://joe-sky.github.io/nornj
基于 Mobx 与 JSX 语法扩大的 Ant Design 表单解决方案
咱们部门团队自2016年起始终主力应用 Mobx 作为 React 状态治理计划,几年来咱们始终受害于它的响应式数据流开发体验非常高效,也很容易优化。
Mobx 适配 Antd 表单可能存在的痛点
尽管对于 Mobx 与 Redux 等谁更优不是本篇文章里要比照的,然而通过几年的应用教训,我总结出 Mobx 在配合国内最风行的 React 组件库 Ant Design 组件,特地是表单验证组件时可能存在的一些开发痛点:
- Ant Design Form 组件举荐的数据存取形式,无奈很顺畅地与 Mobx 的响应式数据流联合
Antd Form 组件原生形式应用 getFieldsValue 和 setFieldsValue (官网文档)来对数据进行存取,这在应用 Mobx 做数据流治理时会遇到一些比拟难堪的场景:
- 申请后端接口把返回的表单字段数据存储在了 Mobx observable 数据中,而后咱们须要把这些数据用 setFieldsValue 办法搁置到 Form 组件实例内,各表单组件数据会更新。但这个数据更新的过程没有用上 observable 响应式个性,感觉对应用 Mobx 来说有点节约;
- 从 Form 组件中获取表单字段值时要用 getFieldsValue。这样取出来间接在 render (或 Mobx computed)中应用时,Mobx 的 observer 不会主动重渲染(重计算),可能与直觉不符:
const Demo = () => { const [form] = Form.useForm(); return useObserver(() => ( <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> //表单值更新时,以下文字不会更新 <i>Note:{form.getFieldValue('note')}</i> <i>Gender:{form.getFieldValue('gender')}</i> </div> ));};
当然,上述场景是有方法解决的。然而无论怎样解决,咱们都会感觉到有两份数据存在:Mobx 状态的数据、以及表单本人的数据。对适应了 Mobx 响应式数据流的开发人员来说,可能会感觉麻烦。
- 局部 Mobx 的 observable 数据,在传入 Ant Design Form 表单组件时须要执行 toJS 转换
这可能是 Mobx observable 这种包装数据类型的硬伤,但像 CheckBox.Group 组件这种,每次传入组件的值都手工执行一次 toJS 转换值为一般数组,也的确有点麻烦。
寻找 Mobx 环境的表单计划 - mobx-react-form
咱们能够找到现有的解决方案:mobx-react-form
它与 Antd Form 基于组件内治理数据的思路是不一样的。mobx-react-form 把表单数据、验证状态等都交给一个含 Mobx observable 成员的非凡构造实例来治理,再通过 JSX 延展操作符 API 告诉到 Form 相干组件。一个简略的例子:
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: 'required|email|string|between:5,25',}, { name: 'password', label: 'Password', placeholder: 'Insert Password', rules: '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>));
mobx-react-form 的数据管理思路无疑是更合乎 Mobx 响应式数据流的。尽管官网没给例子,但它在加一些扩大后应也可适配 Antd Form 组件。但咱们从下面代码不难看出,mobx-react-form 和 Antd Form 原生形式比,可能还有以下几个让人顾虑的方面:
- 用 json 形式定义各表单字段属性,不迭 Antd 的 JSX 语法更合乎 React 环境的特色;
- 用 JSX 延展操作符告诉各表单组件,语法可读性可能不是太好;
- 它的底层验证组件,并没有提供 Antd 采纳的 async-validator。
基于 JSX 扩大的表单计划 - mobxFormData
参考了 mobx-react-form 的数据管理思路,我利用 NornJ 现有的 JSX 扩大能力,开发出了基于 async-validator 的解决方案:mobxFormData ,同时反对Antd v3 & v4,性能也不错。具体文档在这里。
Codesandbox 示例(如果一次无奈运行,多刷新几次就好)
应用形式很简略,装置 preset:
npm install babel-preset-nornj-with-antd
再配一下 Babel:
{ "presets": [ ..., "nornj-with-antd" //通常放在所有 preset 的最初面 ]}
而后就能够在 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(() => ( <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> ));};
如上,此计划的表单字段数据放在 <mobxFormData>
标签返回的 formData 实例中。与 mobx-react-form 思路相似,formData 是一个扁平化的 Mobx observable 数据类型,下面蕴含了各表单数据字段、以及各种表单数据操作 API,应用起来十分不便,能够很好地与 Mobx 数据流对接:
export default props => { const { formData } = useLocalStore(() => ( <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; }); }, []); //表单数据操作 api 都在 formData 实例上,能够把实例传递给其余组件 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> //表单值更新时,以下文字会实时更新 <i>Username:{formData.userName}</i> <i>Password:{formData.password}</i> </div> ));};
- 这里用到的 mobxFormData 是一种 JSX 扩大:标签,它被 Babel 转换后的理论值并不是 React.createElement 办法,而只是返回了非凡的对象构造,供 Mobx 转换为 observable 类型,转换原理请看这里。
- 而 mobxField 是另一种 JSX 扩大:指令,应用它将 formData 实例与 Form.Item 组件建设双向数据绑定。在 mobxField 指令的底层实现中,通过配置对不同的 Antd 表单元素组件选取了特定的值属性、事件属性等进行自动更新,并且曾经在该转换时调用过 Mobx 的 toJS 办法了,无需再手工 toJS。
mobxFormData 计划的语法整体看起来,和 React JSX 环境感觉也比拟符合,IDE 语法提醒也是残缺的。除了语法,它的各方面性能其实也挺全面,Antd 原生 Form 能实现的它也简直都能实现。具体能够看它的文档和示例。
mobxFormData 的各种表单示例文档
为了更好地服务于开发者,mobxFormData 计划依照 antd v4 版官网文档,重写了其中10多个可运行示例文档,并应用 Dumi 部署在 NornJ 的文档站点中:mobxFormData 表单示例文档。
大家能够拿它和 antd 官网表单示例文档 做下比照,其实能够看出在同样性能的状况下,mobxFormData 的代码量通常会更少一些。
mobxFormData 能用于生产环境吗
mobxFormData 计划在我司大部门内已有多个线上理论我的项目在用,所以我感觉如果您认为它对您的开发体验有益处,或有趣味尝试,则能够用于生产环境。作者也会始终保持更新这个我的项目,如果发现问题十分欢迎您的反馈。
对于 JSX 扩大,一些作者的教训
最初,依作者的实践经验,总结出一些作者认为的目前 JSX 扩大计划可行教训,在此分享给大家:
教训一:JSX 扩大其实能反对 IDE 代码提醒
在一些文章评论中,我记得不只一次看到过有人提过: Babel 做的 JSX 扩大是否会无奈与现有的 Eslint 与 IDE 语法提醒环境交融。这里能够给出一个论断:JSX 扩大其实绝大多数都能够反对 IDE 语法提醒。
而办法就是应用 Typescript,只有把握一些 TS 重写类型的常识即可,定义在 global.d.ts 内。例如:
const Test = () => <customDiv id="test">test</customDiv>
为下面的 customDiv 标签补上 TS 类型,只有这样:
interface ICustomDiv { id: string;}declare namespace JSX { interface IntrinsicElements { /** * customDiv tag */ customDiv: ICustomDiv; }}
指令的话,例如:
const Test = () => <div customId="test">test</div>
TS 这样写就能够:
declare namespace JSX { interface IntrinsicAttributes { /** * customId directive */ customId?: string; //因为每个组件都可能用到,为不影响类型查看,所以定义为可选的 }}
NornJ 我的项目所有的预置 JSX 扩大都是这样来定义类型,代码能够看这里。Eslint 的话,如果 TS 类型定义好了它通常不会受影响,但可能用到未应用的变量等,这时也不难解决简略加个配置就好,配置办法能够看这里。
教训二:React 用双向数据绑定的场景其实不等于用指令语法
还有些观点感觉 “双向绑定” 这个概念,仿佛在 React 环境中呈现会是一种不合时宜的场景。
双向绑定的含意了解起来是视图组件和数据模型之间建设的绑定关系,它们会双向同步更新。这种场景 React 中也可能会存在,像 Antd 的 Form 组件,从晚期版本直到最新的 V4 版,在我看来它的数据管理形式其实始终都相似于双向数据绑定,但并没有用指令形式 API 实现。从它的官网文档中,也始终能够看到对双向绑定的形容。
对于指令的实现,不同的 Babel JSX 扩大我的项目的实现也不同,大多数是语法糖转换;也有比拟非凡的,比方 NornJ 的mobxBind 指令,它的实现其实是一个React 高阶组件。所以说 API 只是模式,并不一定代表底层实现。
教训三:目前有哪些现存的 JSX 语法扩大计划
这个畛域的确比拟偏,以下是作者这些年来见过的几个 Babel JSX 扩大我的项目,它们都提供了流程管制等常见 JSX 扩大:
- https://github.com/AlexGiller...
- https://github.com/peakchen90...
- https://github.com/jsx-plus/j...
- https://github.com/bukharim96...
- https://github.com/aardito2/b...
目前可扩大的 Babel JSX 插件除了作者本人开发的 NornJ:
- https://github.com/joe-sky/nornj
临时未找到其余现有的能让开发者扩大的,如果有敌人晓得的话能够通知我,感激????