diff --git a/.umirc.js b/.umirc.js index 964ff18e19c..e317575ce48 100644 --- a/.umirc.js +++ b/.umirc.js @@ -7,78 +7,156 @@ export default { favicon: '//img.alicdn.com/imgextra/i3/O1CN01XtT3Tv1Wd1b5hNVKy_!!6000000002810-55-tps-360-360.svg', outputPath: './doc-site', - navs: [ - { - title: '指南', - path: '/guide', - }, - { - title: '基础核心库', - children: [ - { - title: '@formily/reactive', - path: 'https://reactive.formilyjs.org', - }, - { - title: '@formily/core', - path: 'https://core.formilyjs.org', - }, - { - title: '@formily/react', - path: 'https://react.formilyjs.org', - }, - { - title: '@formily/vue', - path: 'https://vue.formilyjs.org', - }, - ], - }, - { - title: '组件生态', - children: [ - { - title: '@formily/antd', - path: 'https://antd.formilyjs.org', - }, - { - title: '@formily/next', - path: 'https://fusion.formilyjs.org', - }, - ], - }, - { - title: '工具', - children: [ - { - title: '表单编辑器', - path: 'https://github.com/alibaba/formily-editor', - }, - { - title: 'Chrome扩展', - path: - 'https://chrome.google.com/webstore/detail/formily-devtools/kkocalmbfnplecdmbadaapgapdioecfm?hl=zh-CN', - }, - ], - }, - { - title: '社区', - children: [ - { - title: '论坛', - path: 'https://github.com/alibaba/formily/discussions', - }, - { title: '知乎专栏', path: 'https://www.zhihu.com/column/uform' }, - ], - }, - { - title: '1.x文档', - path: 'https://formilyjs.org', - }, - { - title: 'GITHUB', - path: 'https://github.com/alibaba/formily', - }, + locales: [ + ['en-US', 'English'], + ['zh-CN', '中文'], ], + navs: { + 'en-US': [ + { + title: 'Guide', + path: '/guide', + }, + { + title: 'Basic Core Library', + children: [ + { + title: '@formily/reactive', + path: 'https://reactive.formilyjs.org', + }, + { + title: '@formily/core', + path: 'https://core.formilyjs.org', + }, + { + title: '@formily/react', + path: 'https://react.formilyjs.org', + }, + { + title: '@formily/vue', + path: 'https://vue.formilyjs.org', + }, + ], + }, + { + title: 'Component Ecology', + children: [ + { + title: '@formily/antd', + path: 'https://antd.formilyjs.org', + }, + { + title: '@formily/next', + path: 'https://fusion.formilyjs.org', + }, + ], + }, + { + title: 'Tools', + children: [ + { + title: 'Form editor', + path: 'https://github.com/alibaba/formily-editor', + }, + { + title: 'Chrome extension', + path: + 'https://chrome.google.com/webstore/detail/formily-devtools/kkocalmbfnplecdmbadaapgapdioecfm?hl=zh-CN', + }, + ], + }, + { + title: 'Community', + children: [ + { + title: 'forum', + path: 'https://github.com/alibaba/formily/discussions', + }, + { title: 'Zhihu column', path: 'https://www.zhihu.com/column/uform' }, + ], + }, + { + title: 'Document@1.x', + path: 'https://formilyjs.org', + }, + { + title: 'GITHUB', + path: 'https://github.com/alibaba/formily', + }, + ], + 'zh-CN': [ + { + title: '指南', + path: '/zh-CN/guide', + }, + { + title: '基础核心库', + children: [ + { + title: '@formily/reactive', + path: 'https://reactive.formilyjs.org', + }, + { + title: '@formily/core', + path: 'https://core.formilyjs.org', + }, + { + title: '@formily/react', + path: 'https://react.formilyjs.org', + }, + { + title: '@formily/vue', + path: 'https://vue.formilyjs.org', + }, + ], + }, + { + title: '组件生态', + children: [ + { + title: '@formily/antd', + path: 'https://antd.formilyjs.org', + }, + { + title: '@formily/next', + path: 'https://fusion.formilyjs.org', + }, + ], + }, + { + title: '工具', + children: [ + { + title: '表单编辑器', + path: 'https://github.com/alibaba/formily-editor', + }, + { + title: 'Chrome扩展', + path: + 'https://chrome.google.com/webstore/detail/formily-devtools/kkocalmbfnplecdmbadaapgapdioecfm?hl=zh-CN', + }, + ], + }, + { + title: '社区', + children: [ + { + title: '论坛', + path: 'https://github.com/alibaba/formily/discussions', + }, + { title: '知乎专栏', path: 'https://www.zhihu.com/column/uform' }, + ], + }, + { + title: '1.x文档', + path: 'https://formilyjs.org', + }, + { + title: 'GITHUB', + path: 'https://github.com/alibaba/formily', + }, + ], + }, styles: [ `.__dumi-default-navbar-logo{ height: 60px !important; @@ -102,51 +180,153 @@ export default { menus: { '/guide': [ { - title: '介绍', + title: 'Introduction', path: '/guide', }, { - title: '如何学习Formily', + title: 'How to learn Formily', path: '/guide/learn-formily', }, { - title: '快速开始', + title: 'Quick start', path: '/guide/quick-start', }, { - title: 'V2升级指南', + title: 'V2 Upgrade Guide', path: '/guide/upgrade', }, + { + title: 'Scenes', + children: [ + { + title: 'Login&Signup', + path: '/guide/scenes/login-register', + }, + { + title: 'Query List', + path: '/guide/scenes/query-list', + }, + { + title: 'Edit&Details', + path: '/guide/scenes/edit-detail', + }, + { + title: 'Dialog&Drawer', + path: '/guide/scenes/dialog-drawer', + }, + { + title: 'Step Form', + path: '/guide/scenes/step-form', + }, + { + title: 'Tab Form', + path: '/guide/scenes/tab-form', + }, + { + title: 'More Scenes', + path: '/guide/scenes/more', + }, + ], + }, + { + title: 'Advanced Guide', + children: [ + { + title: 'Form Validation', + path: '/guide/advanced/validate', + }, + { + title: 'Form Layout', + path: '/guide/advanced/layout', + }, + { + title: 'Asynchronous Data Sources', + path: '/guide/advanced/async', + }, + { + title: 'Form Controlled', + path: '/guide/advanced/controlled', + }, + { + title: 'Linkage Logic', + path: '/guide/advanced/linkages', + }, + { + title: 'Calculator', + path: '/guide/advanced/calculator', + }, + { + title: 'Custom Components', + path: '/guide/advanced/custom', + }, + { + title: 'Front-end and back-end data compatibility solution', + path: '/guide/advanced/destructor', + }, + { + title: 'Manage Business Logic', + path: '/guide/advanced/business-logic', + }, + { + title: 'Pack on demand', + path: '/guide/advanced/build', + }, + ], + }, + + { + title: 'Contribution Guide', + path: '/guide/contribution', + }, + ], + + '/zh-CN/guide': [ + { + title: '介绍', + path: '/zh-CN/guide', + }, + { + title: '如何学习Formily', + path: '/zh-CN/guide/learn-formily', + }, + { + title: '快速开始', + path: '/zh-CN/guide/quick-start', + }, + { + title: 'V2升级指南', + path: '/zh-CN/guide/upgrade', + }, { title: '场景案例', children: [ { title: '登陆注册', - path: '/guide/scenes/login-register', + path: '/zh-CN/guide/scenes/login-register', }, { title: '查询列表', - path: '/guide/scenes/query-list', + path: '/zh-CN/guide/scenes/query-list', }, { title: '编辑详情', - path: '/guide/scenes/edit-detail', + path: '/zh-CN/guide/scenes/edit-detail', }, { title: '弹窗与抽屉', - path: '/guide/scenes/dialog-drawer', + path: '/zh-CN/guide/scenes/dialog-drawer', }, { title: '分步表单', - path: '/guide/scenes/step-form', + path: '/zh-CN/guide/scenes/step-form', }, { title: '选项卡/手风琴表单', - path: '/guide/scenes/tab-form', + path: '/zh-CN/guide/scenes/tab-form', }, { title: '更多场景', - path: '/guide/scenes/more', + path: '/zh-CN/guide/scenes/more', }, ], }, @@ -155,50 +335,50 @@ export default { children: [ { title: '实现表单校验', - path: '/guide/advanced/validate', + path: '/zh-CN/guide/advanced/validate', }, { title: '实现表单布局', - path: '/guide/advanced/layout', + path: '/zh-CN/guide/advanced/layout', }, { title: '实现异步数据源', - path: '/guide/advanced/async', + path: '/zh-CN/guide/advanced/async', }, { title: '实现表单受控', - path: '/guide/advanced/controlled', + path: '/zh-CN/guide/advanced/controlled', }, { title: '实现联动逻辑', - path: '/guide/advanced/linkages', + path: '/zh-CN/guide/advanced/linkages', }, { title: '实现联动计算器', - path: '/guide/advanced/calculator', + path: '/zh-CN/guide/advanced/calculator', }, { title: '实现自定义组件', - path: '/guide/advanced/custom', + path: '/zh-CN/guide/advanced/custom', }, { title: '前后端数据差异兼容方案', - path: '/guide/advanced/destructor', + path: '/zh-CN/guide/advanced/destructor', }, { title: '管理业务逻辑', - path: '/guide/advanced/business-logic', + path: '/zh-CN/guide/advanced/business-logic', }, { title: '按需打包', - path: '/guide/advanced/build', + path: '/zh-CN/guide/advanced/build', }, ], }, { title: '贡献指南', - path: '/guide/contribution', + path: '/zh-CN/guide/contribution', }, ], }, diff --git a/docs/guide/advanced/async.md b/docs/guide/advanced/async.md index 5206e3b9d4b..0bbf88453dd 100644 --- a/docs/guide/advanced/async.md +++ b/docs/guide/advanced/async.md @@ -1,15 +1,16 @@ -# 实现异步数据源 +# Asynchronous Data Sources -异步数据源管理,核心体现在[Field](https://core.formilyjs.org/api/models/field)模型中的 dataSource 属性,我们可以在 effects 中修改 Field 的 dataSource,也可以在 reactions 中修改 dataSource 属性。 +Asynchronous data source management, the core is reflected in the dataSource property of the [Field](https://core.formilyjs.org/api/models/field) model. We can modify the dataSource of the Field in effects, or modify the dataSource property in reactions. -如果字段组件内部(比如 Select)有消费 dataSource 属性,当 dataSource 发生变化时,对应组件会自动重渲染。 +If the field component (such as Select) has a consumer dataSource property, when the dataSource changes, the corresponding component will automatically re-render. -注意:如果是业务自定义组件,请手动映射dataSource到自定义组件中,可以使用 connect,也可以使用 observer + useField +Note: If it is a business custom component, please manually map the dataSource to the custom component, you can use connect or observer + useField -具体案例可以参考: +Specific cases can refer to: - [Select](https://antd.formilyjs.org/components/select) - [TreeSelect](https://antd.formilyjs.org/components/tree-select) - [Cascader](https://antd.formilyjs.org/components/cascader) + diff --git a/docs/guide/advanced/async.zh-CN.md b/docs/guide/advanced/async.zh-CN.md new file mode 100644 index 00000000000..5206e3b9d4b --- /dev/null +++ b/docs/guide/advanced/async.zh-CN.md @@ -0,0 +1,15 @@ +# 实现异步数据源 + +异步数据源管理,核心体现在[Field](https://core.formilyjs.org/api/models/field)模型中的 dataSource 属性,我们可以在 effects 中修改 Field 的 dataSource,也可以在 reactions 中修改 dataSource 属性。 + +如果字段组件内部(比如 Select)有消费 dataSource 属性,当 dataSource 发生变化时,对应组件会自动重渲染。 + + +注意:如果是业务自定义组件,请手动映射dataSource到自定义组件中,可以使用 connect,也可以使用 observer + useField + + +具体案例可以参考: + +- [Select](https://antd.formilyjs.org/components/select) +- [TreeSelect](https://antd.formilyjs.org/components/tree-select) +- [Cascader](https://antd.formilyjs.org/components/cascader) diff --git a/docs/guide/advanced/build.md b/docs/guide/advanced/build.md index d7985130dd2..aa133d18045 100644 --- a/docs/guide/advanced/build.md +++ b/docs/guide/advanced/build.md @@ -1,19 +1,19 @@ -# 按需打包 +# Pack on Demand -## 基于Umi开发 +## Based on Umi Development -#### 安装 `babel-plugin-import` +#### Install `babel-plugin-import` ```shell npm install babel-plugin-import --save-dev ``` -或者 +or ```shell yarn add babel-plugin-import --dev ``` -#### 插件配置 -修改 `.umirc.js`或 `.umirc.ts` +#### Plugin Configuration +Modify `.umirc.js` or `.umirc.ts` ```js export default { @@ -21,20 +21,20 @@ export default { }; ``` -## 基于create-react-app开发 +## Based on Create-react-app Development - 首先我们需要对`create-react-app`的默认配置进行自定义,这里我们使用 [react-app-rewired](https://github.com/timarney/react-app-rewired) (一个对 `create-react-app` 进行自定义配置的社区解决方案)。 -引入 `react-app-rewired` 并修改 `package.json` 里的启动配置。由于新的 [react-app-rewired@2.x](https://github.com/timarney/react-app-rewired#alternatives) 版本的关系,你还需要安装 [customize-cra](https://github.com/arackaf/customize-cra)。 +First, we need to customize the default configuration of `create-react-app`, here we use [react-app-rewired](https://github.com/timarney/react-app-rewired) (A community solution for custom configuration of `create-react-app`) +Introduce `react-app-rewired` and modify the startup configuration in `package.json`. Due to the new [react-app-rewired@2.x](https://github.com/timarney/react-app-rewired#alternatives) version, you also need to install [customize-cra](https://github.com/arackaf/customize-cra). ```shell $ npm install react-app-rewired customize-cra --save-dev ``` -或者 +or ```shell $ yarn add react-app-rewired customize-cra --dev ``` -修改 `package.json` +modify `package.json` ```diff "scripts": { - "start": "react-scripts start", @@ -45,7 +45,7 @@ $ yarn add react-app-rewired customize-cra --dev + "test": "react-app-rewired test", } ``` -然后在项目根目录创建一个 `config-overrides.js` 用于修改默认配置。 +Then create a `config-overrides.js` in the project root directory to modify the default configuration. ```js module.exports = function override(config, env) { @@ -53,17 +53,17 @@ module.exports = function override(config, env) { return config; }; ``` -#### 安装 babel-plugin-import +#### Install babel-plugin-import ```shell npm install babel-plugin-import --save-dev ``` -或者 +or ```shell yarn add babel-plugin-import --dev ``` -修改`config-overrides.js` +modify `config-overrides.js` ```diff + const { override, fixBabelImports } = require('customize-cra'); @@ -81,19 +81,19 @@ yarn add babel-plugin-import --dev ``` -## 在Webpack中使用 +## Use in Webpack -#### 安装 babel-plugin-import +#### Install babel-plugin-import ```shell npm install babel-plugin-import --save-dev ``` -或者 +or ```shell yarn add babel-plugin-import --dev ``` -修改 `.babelrc` 或者 babel-loader +Modify `.babelrc` or babel-loader ```json { @@ -103,4 +103,5 @@ yarn add babel-plugin-import --dev } ``` -更多配置请参考 [babel-plugin-import](https://github.com/ant-design/babel-plugin-import) +For more configuration, please refer to [babel-plugin-import](https://github.com/ant-design/babel-plugin-import) + diff --git a/docs/guide/advanced/build.zh-CN.md b/docs/guide/advanced/build.zh-CN.md new file mode 100644 index 00000000000..d7985130dd2 --- /dev/null +++ b/docs/guide/advanced/build.zh-CN.md @@ -0,0 +1,106 @@ +# 按需打包 + +## 基于Umi开发 + +#### 安装 `babel-plugin-import` + +```shell +npm install babel-plugin-import --save-dev +``` +或者 +```shell +yarn add babel-plugin-import --dev +``` + +#### 插件配置 +修改 `.umirc.js`或 `.umirc.ts` + +```js +export default { + extraBabelPlugins: [['babel-plugin-import', {"libraryName": "@formily/antd", "libraryDirectory": "lib"}]], +}; +``` + +## 基于create-react-app开发 + + 首先我们需要对`create-react-app`的默认配置进行自定义,这里我们使用 [react-app-rewired](https://github.com/timarney/react-app-rewired) (一个对 `create-react-app` 进行自定义配置的社区解决方案)。 +引入 `react-app-rewired` 并修改 `package.json` 里的启动配置。由于新的 [react-app-rewired@2.x](https://github.com/timarney/react-app-rewired#alternatives) 版本的关系,你还需要安装 [customize-cra](https://github.com/arackaf/customize-cra)。 + +```shell +$ npm install react-app-rewired customize-cra --save-dev +``` +或者 +```shell +$ yarn add react-app-rewired customize-cra --dev +``` + +修改 `package.json` +```diff +"scripts": { +- "start": "react-scripts start", ++ "start": "react-app-rewired start", +- "build": "react-scripts build", ++ "build": "react-app-rewired build", +- "test": "react-scripts test", ++ "test": "react-app-rewired test", +} +``` +然后在项目根目录创建一个 `config-overrides.js` 用于修改默认配置。 + +```js +module.exports = function override(config, env) { + // do stuff with the webpack config... + return config; +}; +``` +#### 安装 babel-plugin-import + +```shell +npm install babel-plugin-import --save-dev +``` +或者 +```shell +yarn add babel-plugin-import --dev +``` + +修改`config-overrides.js` + +```diff ++ const { override, fixBabelImports } = require('customize-cra'); + +- module.exports = function override(config, env) { +- // do stuff with the webpack config... +- return config; +- }; ++ module.exports = override( ++ fixBabelImports('import', { ++ libraryName: '@formily/antd', ++ libraryDirectory: 'lib' ++ }), ++ ); +``` + + +## 在Webpack中使用 + +#### 安装 babel-plugin-import + +```shell +npm install babel-plugin-import --save-dev +``` +或者 +```shell +yarn add babel-plugin-import --dev +``` + +修改 `.babelrc` 或者 babel-loader + +```json +{ + "plugins": [ + ["import", { "libraryName": "@formily/antd", "libraryDirectory": "lib"}] + ] +} +``` + +更多配置请参考 [babel-plugin-import](https://github.com/ant-design/babel-plugin-import) diff --git a/docs/guide/advanced/business-logic.md b/docs/guide/advanced/business-logic.md index d76eb4ed186..91f5c76d478 100644 --- a/docs/guide/advanced/business-logic.md +++ b/docs/guide/advanced/business-logic.md @@ -1,39 +1,40 @@ -# 管理业务逻辑 +# Manage Business Logic -在前面的文档中,我们其实可以发现 Formily 已经提供了局部描述逻辑的能力,也就是字段组件的 x-reactions/reactions 属性,而且在 Schema 中,x-reactions 既能传函数,也能传一个结构化对象,当然,还有 Formily1.x 继承下来的 effects,那么总结一下,在 Formily2.x 中描述逻辑的方式有: +In the previous document, we can actually find that Formily has provided the ability to describe the logic locally, that is, the x-reactions/reactions property of the field component. And in Schema, x-reactions can pass both functions and a structured object. Of course, there are also effects inherited from Formily 1.x, So to summarize, the ways to describe logic in Formily 2.x are: -- 纯 JSX 模式下的 effects 或 reactions 属性 -- Schema 模式下的 effects 或结构化 x-reactions 属性 -- Schema 模式下的 effects 或函数态 x-reactions 属性 +- Effects or reactions property in pure JSX mode +- Effects or structured x-reactions property in Schema mode +- Effects or functional x-reactions property in Schema mode -这么多描述逻辑的方式,我们该如何选择?什么场景下是最佳实践呢?首先,我们要理解清楚 effects 和 reactions 的定位。 +With so many ways of describing logic, how should we choose? What scenarios are best practices? First, we need to understand the positioning of effects and reactions. -首先,reactions 是用在具体字段属性上的响应器,它会基于函数内依赖的数据变化而重复执行,它最大的优点就是简单直接,容易理解,比如: +First of all, reactions are responders used on specific field properties. They will be executed repeatedly based on the data changes that the function depends on. Its biggest advantage is that it is simple, straightforward and easy to understand, such as: ```tsx pure { - /**具体逻辑实现**/ + /**specific logic implementation**/ }} /> ``` -然后,effects 是用于实现副作用隔离逻辑管理模型,它最大的优点就是在字段数量超多的场景下,可以让视图代码变得更易维护,同时它还有一个能力,就是可以批量化的对字段做处理。比如我们在 A,B,C 字段属性显示声明 x-reactions,如果这 3 个字段的 x-reactions 逻辑都是一模一样的,那我们在 effects 中只需这么写即可: +Then, effects are used to implement the side-effect isolation logic management model. Its biggest advantage is that it can make the view code easier to maintain in a scenario with a large number of fields. At the same time, it also has the ability to process fields in batches. For example, we declare x-reactions in the field properties of A, B, C. If the x-reactions logic of these three fields are exactly the same, then we only need to write this in effects: ```ts onFieldReact('*(A,B,C)', (field) => { - //...逻辑 + //...logic }) ``` -使用 effects 还有一个好处就是可以实现一系列的可复用逻辑插件,可以做到很方便的逻辑可拔插,同时还能做一些全局监控之类的事情。 +Another advantage of using effects is that a series of reusable logic plug-ins can be implemented, which can be very convenient logic pluggable, and at the same time can do some things like global monitoring. -这样看来,是不是我们就不需要局部定义逻辑了? +In this way, do we not need to define the logic locally? -并不是,上面的写法的前提是对于字段数量很多,如果视图层满屏的 reactions,看着是很难受的,所以考虑将逻辑抽离统一维护则是一个比较好的策略。相反,如果字段数量很少,逻辑相对简单的,直接在字段属性上写 reactions 也是不错的,清晰明了。 +No, the premise of the above writing is that for a large number of fields, if the view layer is full of reactions, it looks uncomfortable, so it is a better strategy to consider extracting logic from unified maintenance. +On the contrary, if the number of fields is small and the logic is relatively simple, it is also good to write reactions directly on the field attributes, which is clear. -同时,因为 JSON Schema 是可以给配置化系统消费的,我们需要在配置界面上对具体某个字段做逻辑配置。所以我们还是需要支持局部定义逻辑能力,同时还需要支持结构化描述逻辑,比如: +At the same time, because JSON Schema can be consumed by the configuration system, we need to logically configure a specific field on the configuration interface. So we still need to support local definition logic capabilities, and also need to support structured description logic, such as: ```json { @@ -48,15 +49,15 @@ onFieldReact('*(A,B,C)', (field) => { } ``` -这样可以很好的解决大部分配置场景的联动需求了,但是,还有一种场景,就是我们的联动过程是存在异步的,逻辑非常复杂的,或者存在大量数据处理的,那我们就只能考虑开放函数态描述的能力了,比如: +This can well solve the linkage requirements of most configuration scenarios. However, there is another scenario, that is, our linkage process is asynchronous, the logic is very complicated, or there is a large amount of data processing, then we can only consider open up the ability to describe functional states, such as: ```json { - "x-reactions": "{{(field)=>{/**具体逻辑实现**/}}}" + "x-reactions": "{{(field)=>{/**specific logic implementation**/}}}" } ``` -这种就很像是低代码配置了,当然,我们也可以在上下文作用域中注册一系列的通用逻辑函数: +This is very similar to a low-code configuration. Of course, we can also register a series of general logic functions in the context scope: ```json { @@ -64,13 +65,13 @@ onFieldReact('*(A,B,C)', (field) => { } ``` -最终总结下来,我们管理业务逻辑的方式,有以下优先级: +In conclusion, the way we manage business logic has the following priorities: -- 纯源码模式 - - 字段数量庞大,逻辑复杂,优先选择 effects 中定义逻辑 - - 字段数量少,逻辑简单,优先选择 reactions 中定义逻辑 -- Schema 模式 - - 不存在异步逻辑,优先选择结构化 reactions 定义逻辑 - - 存在异步逻辑,或者大量计算,优先选择函数态 reactions 定义逻辑 +- Pure source mode + - The number of fields is huge and the logic is complex, and the logic defined in effects is preferred. + - The number of fields is small, the logic is simple, and the logic defined in reactions is preferred +- Schema mode + - There is no asynchronous logic, structured reactions are preferred to define logic. + - There is asynchronous logic, or a large number of calculations, the functional state reactions are preferred to define logic. -对于 effects 中如何玩出花来,我们主要看[@formily/core](https://core.formilyjs.org)文档即可 +For how to play with effects in effects, we mainly look at the [@formily/core](https://core.formilyjs.org) document. \ No newline at end of file diff --git a/docs/guide/advanced/business-logic.zh-CN.md b/docs/guide/advanced/business-logic.zh-CN.md new file mode 100644 index 00000000000..d76eb4ed186 --- /dev/null +++ b/docs/guide/advanced/business-logic.zh-CN.md @@ -0,0 +1,76 @@ +# 管理业务逻辑 + +在前面的文档中,我们其实可以发现 Formily 已经提供了局部描述逻辑的能力,也就是字段组件的 x-reactions/reactions 属性,而且在 Schema 中,x-reactions 既能传函数,也能传一个结构化对象,当然,还有 Formily1.x 继承下来的 effects,那么总结一下,在 Formily2.x 中描述逻辑的方式有: + +- 纯 JSX 模式下的 effects 或 reactions 属性 +- Schema 模式下的 effects 或结构化 x-reactions 属性 +- Schema 模式下的 effects 或函数态 x-reactions 属性 + +这么多描述逻辑的方式,我们该如何选择?什么场景下是最佳实践呢?首先,我们要理解清楚 effects 和 reactions 的定位。 + +首先,reactions 是用在具体字段属性上的响应器,它会基于函数内依赖的数据变化而重复执行,它最大的优点就是简单直接,容易理解,比如: + +```tsx pure + { + /**具体逻辑实现**/ + }} +/> +``` + +然后,effects 是用于实现副作用隔离逻辑管理模型,它最大的优点就是在字段数量超多的场景下,可以让视图代码变得更易维护,同时它还有一个能力,就是可以批量化的对字段做处理。比如我们在 A,B,C 字段属性显示声明 x-reactions,如果这 3 个字段的 x-reactions 逻辑都是一模一样的,那我们在 effects 中只需这么写即可: + +```ts +onFieldReact('*(A,B,C)', (field) => { + //...逻辑 +}) +``` + +使用 effects 还有一个好处就是可以实现一系列的可复用逻辑插件,可以做到很方便的逻辑可拔插,同时还能做一些全局监控之类的事情。 + +这样看来,是不是我们就不需要局部定义逻辑了? + +并不是,上面的写法的前提是对于字段数量很多,如果视图层满屏的 reactions,看着是很难受的,所以考虑将逻辑抽离统一维护则是一个比较好的策略。相反,如果字段数量很少,逻辑相对简单的,直接在字段属性上写 reactions 也是不错的,清晰明了。 + +同时,因为 JSON Schema 是可以给配置化系统消费的,我们需要在配置界面上对具体某个字段做逻辑配置。所以我们还是需要支持局部定义逻辑能力,同时还需要支持结构化描述逻辑,比如: + +```json +{ + "x-reactions": { + "dependencies": ["aa"], + "fulfill": { + "state": { + "visible": "$deps[0] == '123'" + } + } + } +} +``` + +这样可以很好的解决大部分配置场景的联动需求了,但是,还有一种场景,就是我们的联动过程是存在异步的,逻辑非常复杂的,或者存在大量数据处理的,那我们就只能考虑开放函数态描述的能力了,比如: + +```json +{ + "x-reactions": "{{(field)=>{/**具体逻辑实现**/}}}" +} +``` + +这种就很像是低代码配置了,当然,我们也可以在上下文作用域中注册一系列的通用逻辑函数: + +```json +{ + "x-reactions": "{{customFunction}}" +} +``` + +最终总结下来,我们管理业务逻辑的方式,有以下优先级: + +- 纯源码模式 + - 字段数量庞大,逻辑复杂,优先选择 effects 中定义逻辑 + - 字段数量少,逻辑简单,优先选择 reactions 中定义逻辑 +- Schema 模式 + - 不存在异步逻辑,优先选择结构化 reactions 定义逻辑 + - 存在异步逻辑,或者大量计算,优先选择函数态 reactions 定义逻辑 + +对于 effects 中如何玩出花来,我们主要看[@formily/core](https://core.formilyjs.org)文档即可 diff --git a/docs/guide/advanced/calculator.md b/docs/guide/advanced/calculator.md index a0d1c793a32..7476f02cadd 100644 --- a/docs/guide/advanced/calculator.md +++ b/docs/guide/advanced/calculator.md @@ -1,8 +1,8 @@ -# 实现联动计算器 +# Calculator -联动计算器,主要用于在填写表单的过程中做求值汇总,在 Formily1.x 中实现这类需求的成本非常非常高,在 2.x 中,我们可以借助 reactions 轻松实现 +Linkage calculator is mainly used for evaluation and summarization in the process of filling in the form. In Formily 1.x, the cost of realizing this kind of demand is very high. In 2.x, we can easily implement it with the help of reactions. -## Markup Schema 案例 +## Markup Schema Case ```tsx import React from 'react' @@ -158,7 +158,7 @@ export default () => { } ``` -## JSON Schema 案例 +## JSON Schema Case ```tsx import React from 'react' @@ -357,9 +357,10 @@ export default () => {
- 提交 + submit ) } ``` + diff --git a/docs/guide/advanced/calculator.zh-CN.md b/docs/guide/advanced/calculator.zh-CN.md new file mode 100644 index 00000000000..a0d1c793a32 --- /dev/null +++ b/docs/guide/advanced/calculator.zh-CN.md @@ -0,0 +1,365 @@ +# 实现联动计算器 + +联动计算器,主要用于在填写表单的过程中做求值汇总,在 Formily1.x 中实现这类需求的成本非常非常高,在 2.x 中,我们可以借助 reactions 轻松实现 + +## Markup Schema 案例 + +```tsx +import React from 'react' +import { + Form, + FormItem, + NumberPicker, + ArrayTable, + Editable, + Input, + FormButtonGroup, + Submit, +} from '@formily/antd' +import { createForm } from '@formily/core' +import { createSchemaField } from '@formily/react' +import 'antd/lib/alert/style' + +const SchemaField = createSchemaField({ + components: { + FormItem, + Editable, + Input, + NumberPicker, + ArrayTable, + }, +}) + +const form = createForm() + +export default () => { + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0}}', + fulfill: { + state: { + value: + '{{$deps[0].reduce((total,item)=>item.total ? total+item.total : total,0)}}', + }, + }, + }} + /> + + + 提交 + +
+ ) +} +``` + +## JSON Schema 案例 + +```tsx +import React from 'react' +import { + Form, + FormItem, + NumberPicker, + ArrayTable, + Editable, + Input, + FormButtonGroup, + Submit, +} from '@formily/antd' +import { createForm } from '@formily/core' +import { createSchemaField } from '@formily/react' +import 'antd/lib/alert/style' + +const SchemaField = createSchemaField({ + components: { + FormItem, + Editable, + Input, + NumberPicker, + ArrayTable, + }, +}) + +const form = createForm() + +const schema = { + type: 'object', + properties: { + projects: { + type: 'array', + title: 'Projects', + 'x-decorator': 'FormItem', + 'x-component': 'ArrayTable', + items: { + type: 'object', + properties: { + column_1: { + type: 'void', + 'x-component': 'ArrayTable.Column', + 'x-component-props': { + width: 50, + title: 'Sort', + align: 'center', + }, + properties: { + sortable: { + type: 'void', + 'x-component': 'ArrayTable.SortHandle', + }, + }, + }, + column_2: { + type: 'void', + 'x-component': 'ArrayTable.Column', + 'x-component-props': { + width: 50, + title: 'Index', + align: 'center', + }, + properties: { + index: { + type: 'void', + 'x-component': 'ArrayTable.Index', + }, + }, + }, + column_3: { + type: 'void', + 'x-component': 'ArrayTable.Column', + 'x-component-props': { + title: 'Price', + }, + properties: { + price: { + type: 'number', + default: 0, + 'x-decorator': 'Editable', + 'x-component': 'NumberPicker', + 'x-component-props': { + addonAfter: '$', + }, + }, + }, + }, + column_4: { + type: 'void', + 'x-component': 'ArrayTable.Column', + 'x-component-props': { + title: 'Count', + }, + properties: { + count: { + type: 'number', + default: 0, + 'x-decorator': 'Editable', + 'x-component': 'NumberPicker', + 'x-component-props': { + addonAfter: '$', + }, + }, + }, + }, + column_5: { + type: 'void', + 'x-component': 'ArrayTable.Column', + 'x-component-props': { + title: 'Total', + }, + properties: { + total: { + type: 'number', + 'x-read-pretty': true, + 'x-decorator': 'FormItem', + 'x-component': 'NumberPicker', + 'x-component-props': { + addonAfter: '$', + }, + 'x-reactions': { + dependencies: ['.price', '.count'], + when: '{{$deps[0] && $deps[1]}}', + fulfill: { + state: { + value: '{{$deps[0] * $deps[1]}}', + }, + }, + }, + }, + }, + }, + column_6: { + type: 'void', + 'x-component': 'ArrayTable.Column', + 'x-component-props': { + title: 'Operations', + }, + properties: { + item: { + type: 'void', + 'x-component': 'FormItem', + properties: { + remove: { + type: 'void', + 'x-component': 'ArrayTable.Remove', + }, + moveDown: { + type: 'void', + 'x-component': 'ArrayTable.MoveDown', + }, + moveUp: { + type: 'void', + 'x-component': 'ArrayTable.MoveUp', + }, + }, + }, + }, + }, + }, + }, + properties: { + add: { + type: 'void', + title: 'Add', + 'x-component': 'ArrayTable.Addition', + }, + }, + }, + total: { + type: 'number', + title: 'Total', + 'x-decorator': 'FormItem', + 'x-component': 'NumberPicker', + 'x-component-props': { + addonAfter: '$', + }, + 'x-pattern': 'readPretty', + 'x-reactions': { + dependencies: ['.projects'], + when: '{{$deps.length > 0}}', + fulfill: { + state: { + value: + '{{$deps[0].reduce((total,item)=>item.total ? total+item.total : total,0)}}', + }, + }, + }, + }, + }, +} + +export default () => { + return ( +
+ + + 提交 + + + ) +} +``` diff --git a/docs/guide/advanced/controlled.md b/docs/guide/advanced/controlled.md index a1adb86ea56..2a8f4fdbe60 100644 --- a/docs/guide/advanced/controlled.md +++ b/docs/guide/advanced/controlled.md @@ -1,14 +1,14 @@ -# 实现表单受控 +# Form Controlled -Formily2.x 已经放弃了给表单组件和字段组件支持受控模式,因为表单内部管理状态模式本身就不是受控模式,在将受控模式转为非受控模式的过程中会有很多边界问题,同时受控模式会存在大量的脏检查过程,性能很不好,反而非受控模式本身就可以解决大部分问题了。 +Formily 2.x has given up supporting controlled mode for form components and field components. Because the internal management state mode of the form itself is not a controlled mode, there will be many boundary problems in the process of changing the controlled mode to the uncontrolled mode. At the same time, the controlled mode will have a large number of dirty inspection processes, and the performance is very poor. Instead, the controlled mode itself can solve most of the problems. -所以 Formily 就不再支持受控模式了,但是如果我们硬要实现普通 React 受控,还是可以支持的,只不过只能实现值受控,不能实现字段级受控,也就是我们使用的 Field 组件,属性只会在初次渲染时生效,未来属性发生任何变化都不会自动更新,想要自动更新,除非重新创建 Form 实例(显然这样会丢失所有之前维护好的状态)。 +So Formily no longer supports the controlled mode, but if we insist on implementing ordinary React controlled, we can still support it. It can only achieve value control, not field-level control, which is the Field component we use. The properties will only take effect during the first rendering. Any changes to the properties in the future will not be automatically updated. If you want to update automatically, unless you recreate the Form instance (obviously this will lose all the previously maintained state). -所以,我们更加推荐的是使用[@formily/reactive](https://reactive.formilyjs.org) 实现响应式受控,既能实现值受控,也能实现字段级受控 +Therefore, we more recommend using [@formily/reactive](https://reactive.formilyjs.org) to achieve responsive control, which can achieve both value control and field-level control. -## 值受控 +## Value Controlled -普通受控模式,会强依赖脏检查实现数据同步,同时组件渲染次数会非常高 +Ordinary controlled mode, which will rely heavily on dirty checking to achieve data synchronization, and the number of component renderings will be very high. ```tsx import React, { useMemo, useState, useEffect, useRef } from 'react' @@ -49,10 +49,10 @@ const MyForm = (props) => { name="input" x-decorator="FormItem" x-component="Input" - x-component-props={{ placeholder: '受控者' }} + x-component-props={{ placeholder: 'controlled target' }} />
- Form组件渲染次数:{count.current++} + Form component rendering times:{count.current++} ) } @@ -65,7 +65,7 @@ export default () => { { setValues({ ...values, input: event.target.value }) }} @@ -77,18 +77,18 @@ export default () => { setValues({ ...values }) }} /> - 根组件渲染次数:{count.current++} + root component rendering times: {count.current++} ) } ``` -## 响应式值受控 +## Responsive Value Controlled -响应式受控主要是使用[@formily/reactive](https://reactive.formilyjs.org)实现响应式更新,我们可以轻松实现双向绑定,同时性能完爆普通受控更新 +Responsive control is mainly to use [@formily/reactive](https://reactive.formilyjs.org) to achieve responsive updates, we can easily achieve two-way binding, while the performance is full of normal controlled updates. ```tsx -import React, { useMemo, useState, useEffect, useRef } from 'react' +import React, { useMemo, useRef } from 'react' import { createForm } from '@formily/core' import { createSchemaField } from '@formily/react' import { Form, FormItem, Input } from '@formily/antd' @@ -119,10 +119,10 @@ const MyForm = (props) => { name="input" x-decorator="FormItem" x-component="Input" - x-component-props={{ placeholder: '受控者' }} + x-component-props={{ placeholder: 'controlled target' }} />
- Form组件渲染次数:{count.current++} + Form component rendering times:{count.current++} ) } @@ -133,12 +133,12 @@ const Controller = observer((props) => { { props.values.input = event.target.value }} /> - Controller组件渲染次数:{count.current++} + Controller component rendering times:{count.current++} ) }) @@ -154,18 +154,18 @@ export default () => { <> - 根组件渲染次数:{count.current++} + root component rendering times:{count.current++} ) } ``` -## Schema 受控 +## Schema Controlled -对于表单配置化场景会有一个需求,表单的 Schema 会发生频繁改变,其实就相当于频繁创建新表单了,之前操作的状态就应该丢弃了 +There will be a requirement for the form configuration scenario. The Schema of the form will change frequently. In fact, it is equivalent to frequently creating new forms. The state of the previous operation should be discarded. ```tsx -import React, { useMemo, useState, useEffect } from 'react' +import React, { useMemo, useState } from 'react' import { createForm } from '@formily/core' import { createSchemaField } from '@formily/react' import { Form, FormItem, Input, Select } from '@formily/antd' @@ -245,11 +245,11 @@ export default () => { } ``` -## 字段级受控 +## Field Level Control -### 最佳实践 +### Best Practices -推荐使用[@formily/reactive](https://reactive.formilyjs.org) 实现响应式受控 +It is recommended to use [@formily/reactive](https://reactive.formilyjs.org) to achieve responsive control. ```tsx import React from 'react' @@ -277,7 +277,7 @@ const Controller = observer(() => { { obs.input = event.target.value }} @@ -296,9 +296,9 @@ export default () => { name="input" x-decorator="FormItem" x-component="Input" - x-component-props={{ placeholder: '受控者' }} + x-component-props={{ placeholder: 'controlled target' }} x-reactions={(field) => { - field.component[1].placeholder = obs.input || '受控者' + field.component[1].placeholder = obs.input || 'controlled target' }} /> @@ -308,9 +308,9 @@ export default () => { } ``` -### 反模式 +### Anti-pattern -使用传统受控模式是无法自动更新的 +It is not possible to update automatically when using traditional controlled mode. ```tsx import React, { useState } from 'react' @@ -334,7 +334,7 @@ export default () => { { setValue(event.target.value) }} @@ -346,7 +346,7 @@ export default () => { name="input" x-decorator="FormItem" x-component="Input" - x-component-props={{ placeholder: value || '受控者' }} + x-component-props={{ placeholder: value || 'controlled target' }} /> diff --git a/docs/guide/advanced/controlled.zh-CN.md b/docs/guide/advanced/controlled.zh-CN.md new file mode 100644 index 00000000000..6f00bd3cfee --- /dev/null +++ b/docs/guide/advanced/controlled.zh-CN.md @@ -0,0 +1,356 @@ +# 实现表单受控 + +Formily2.x 已经放弃了给表单组件和字段组件支持受控模式,因为表单内部管理状态模式本身就不是受控模式,在将受控模式转为非受控模式的过程中会有很多边界问题,同时受控模式会存在大量的脏检查过程,性能很不好,反而非受控模式本身就可以解决大部分问题了。 + +所以 Formily 就不再支持受控模式了,但是如果我们硬要实现普通 React 受控,还是可以支持的,只不过只能实现值受控,不能实现字段级受控,也就是我们使用的 Field 组件,属性只会在初次渲染时生效,未来属性发生任何变化都不会自动更新,想要自动更新,除非重新创建 Form 实例(显然这样会丢失所有之前维护好的状态)。 + +所以,我们更加推荐的是使用[@formily/reactive](https://reactive.formilyjs.org) 实现响应式受控,既能实现值受控,也能实现字段级受控 + +## 值受控 + +普通受控模式,会强依赖脏检查实现数据同步,同时组件渲染次数会非常高 + +```tsx +import React, { useMemo, useState, useEffect, useRef } from 'react' +import { createForm, onFormValuesChange } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { Form, FormItem, Input } from '@formily/antd' + +const SchemaField = createSchemaField({ + components: { + Input, + FormItem, + }, +}) + +const MyForm = (props) => { + const form = useMemo( + () => + createForm({ + values: props.values, + effects: () => { + onFormValuesChange((form) => { + props.onChange(form.values) + }) + }, + }), + [] + ) + const count = useRef(1) + + useEffect(() => { + form.setValues(props.values, 'overwrite') + }, [JSON.stringify(props.values)]) + + return ( +
+ + + + Form组件渲染次数:{count.current++} +
+ ) +} + +export default () => { + const [values, setValues] = useState({ input: '' }) + const count = useRef(1) + return ( + <> + + { + setValues({ ...values, input: event.target.value }) + }} + /> + + { + setValues({ ...values }) + }} + /> + 根组件渲染次数:{count.current++} + + ) +} +``` + +## 响应式值受控 + +响应式受控主要是使用[@formily/reactive](https://reactive.formilyjs.org)实现响应式更新,我们可以轻松实现双向绑定,同时性能完爆普通受控更新 + +```tsx +import React, { useMemo, useRef } from 'react' +import { createForm } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { Form, FormItem, Input } from '@formily/antd' +import { observable } from '@formily/reactive' +import { observer } from '@formily/reactive-react' + +const SchemaField = createSchemaField({ + components: { + Input, + FormItem, + }, +}) + +const MyForm = (props) => { + const count = useRef(1) + const form = useMemo( + () => + createForm({ + values: props.values, + }), + [] + ) + + return ( +
+ + + + Form组件渲染次数:{count.current++} +
+ ) +} + +const Controller = observer((props) => { + const count = useRef(1) + return ( + + { + props.values.input = event.target.value + }} + /> + Controller组件渲染次数:{count.current++} + + ) +}) + +export default () => { + const count = useRef(1) + const values = useMemo(() => + observable({ + input: '', + }) + ) + return ( + <> + + + 根组件渲染次数:{count.current++} + + ) +} +``` + +## Schema 受控 + +对于表单配置化场景会有一个需求,表单的 Schema 会发生频繁改变,其实就相当于频繁创建新表单了,之前操作的状态就应该丢弃了 + +```tsx +import React, { useMemo, useState } from 'react' +import { createForm } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { Form, FormItem, Input, Select } from '@formily/antd' +import { Button, Space } from 'antd' + +const SchemaField = createSchemaField({ + components: { + Input, + FormItem, + Select, + }, +}) + +export default () => { + const [current, setCurrent] = useState({}) + const form = useMemo(() => createForm(), [current]) + return ( +
+ + + + + + + ) +} +``` + +## 字段级受控 + +### 最佳实践 + +推荐使用[@formily/reactive](https://reactive.formilyjs.org) 实现响应式受控 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { Form, FormItem, Input } from '@formily/antd' +import { observable } from '@formily/reactive' +import { observer } from '@formily/reactive-react' + +const SchemaField = createSchemaField({ + components: { + Input, + FormItem, + }, +}) + +const form = createForm() + +const obs = observable({ + input: '', +}) + +const Controller = observer(() => { + return ( + + { + obs.input = event.target.value + }} + /> + + ) +}) + +export default () => { + return ( + <> + +
+ + { + field.component[1].placeholder = obs.input || '受控者' + }} + /> + +
+ + ) +} +``` + +### 反模式 + +使用传统受控模式是无法自动更新的 + +```tsx +import React, { useState } from 'react' +import { createForm } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { Form, FormItem, Input } from '@formily/antd' + +const SchemaField = createSchemaField({ + components: { + Input, + FormItem, + }, +}) + +const form = createForm() + +export default () => { + const [value, setValue] = useState('') + return ( + <> + + { + setValue(event.target.value) + }} + /> + +
+ + + +
+ + ) +} +``` diff --git a/docs/guide/advanced/custom.md b/docs/guide/advanced/custom.md index df57c68d399..fa041384036 100644 --- a/docs/guide/advanced/custom.md +++ b/docs/guide/advanced/custom.md @@ -1,7 +1,8 @@ -# 实现自定义组件 +# Custom Components -实现业务自定义组件主要是使用[@formily/react](https://react.formilyjs.org) 或[@formily/vue](https://vue.formilyjs.org)中的 Hooks API 与 observer API +The realization of business custom components mainly uses the Hooks API and observer API in [@formily/react](https://react.formilyjs.org) or [@formily/vue](https://vue.formilyjs.org). -接入现成组件库的话,我们主要使用 connect/mapProps/mapReadPretty API +To access the ready-made component library, we mainly use connect/mapProps/mapReadPretty API. + +If you want to implement some more complex custom components, we strongly recommend looking directly at the source code of [@formily/antd](https://github.com/alibaba/formily/tree/master/packages/antd/src) or [@formily/next](https://github.com/alibaba/formily/tree/master/packages/next/src). -如果想要实现一些更复杂的自定义组件,我们强烈推荐直接看[@formily/antd](https://github.com/alibaba/formily/tree/master/packages/antd/src)或 [@formily/next](https://github.com/alibaba/formily/tree/master/packages/next/src)的源码 diff --git a/docs/guide/advanced/custom.zh-CN.md b/docs/guide/advanced/custom.zh-CN.md new file mode 100644 index 00000000000..df57c68d399 --- /dev/null +++ b/docs/guide/advanced/custom.zh-CN.md @@ -0,0 +1,7 @@ +# 实现自定义组件 + +实现业务自定义组件主要是使用[@formily/react](https://react.formilyjs.org) 或[@formily/vue](https://vue.formilyjs.org)中的 Hooks API 与 observer API + +接入现成组件库的话,我们主要使用 connect/mapProps/mapReadPretty API + +如果想要实现一些更复杂的自定义组件,我们强烈推荐直接看[@formily/antd](https://github.com/alibaba/formily/tree/master/packages/antd/src)或 [@formily/next](https://github.com/alibaba/formily/tree/master/packages/next/src)的源码 diff --git a/docs/guide/advanced/destructor.md b/docs/guide/advanced/destructor.md index 7b627d966a6..66b958b24d8 100644 --- a/docs/guide/advanced/destructor.md +++ b/docs/guide/advanced/destructor.md @@ -1,14 +1,14 @@ -# 前后端数据差异兼容方案 +# Compatible solution for front-end and back-end data differences -很多时候,我们总会遇到前端数据结构与后端数据结构不匹配的场景,看似很简单的问题,其实解决起来非常的让人难受,最常见的问题就是: +Many times, we always encounter scenarios where the front-end data structure does not match the back-end data structure. The seemingly simple problem is actually very uncomfortable to solve. The most common problems are: -前端日期范围组件输出的是数组结构,但是后端要求的格式是拆分扁平数据结构,这种问题很大程度是受后端领域模型所限制,因为从后端模型设计的角度来看,拆分扁平结构是最佳方案; +The output of the front-end date range component is an array structure, but the format required by the back-end is to split a flat data structure. This problem is largely limited by the back-end domain model. Because from the perspective of back-end model design, splitting the flat structure is the best solution; -但从前端组件化角度来看,数组结构又是最佳的; +But from the perspective of front-end componentization, the array structure is the best; -所以哪一边都有其道理,可惜的是,每次都只能前端取消化这样一个不平等条约,不过,有了 Formily,你就完全不需要为这样一个尴尬局面而难受了,**Formily 提供了解构路径的能力,可以帮助用户快速解决这类问题。**,下面可以看看例子 +So each side has its truth, but unfortunately, it can only cancel such an unequal treaty at the front end every time. However, with Formily, you don’t need to feel uncomfortable for such an embarrassing situation. **Formily provides the ability to deconstruct the path, which can help users quickly solve such problems.** Let's take a look at an example -## Markup Schema 案例 +## Markup Schema Case ```tsx import React from 'react' @@ -48,24 +48,24 @@ export default () => { { - 提交 + submit ) } ``` -## JSON Schema 案例 +## JSON Schema Cases ```tsx import React from 'react' @@ -117,24 +117,24 @@ const schema = { properties: { visible_destructor: { type: 'boolean', - title: '是否显示解构字段', + title: 'Whether to display deconstructed fields', default: true, enum: [ - { label: '是', value: true }, - { label: '否', value: false }, + { label: 'yes', value: true }, + { label: 'no', value: false }, ], 'x-decorator': 'FormItem', 'x-component': 'Radio.Group', }, undestructor: { type: 'string', - title: '解构前', + title: 'before deconstruction', 'x-decorator': 'FormItem', 'x-component': 'DatePicker.RangePicker', }, '[startDate,endDate]': { type: 'string', - title: '解构后', + title: 'after deconstruction', default: ['2020-11-20', '2021-12-30'], 'x-decorator': 'FormItem', 'x-component': 'DatePicker.RangePicker', @@ -162,14 +162,14 @@ export default () => { - 提交 + submit ) } ``` -## 纯 JSX 案例 +## Pure JSX Case ```tsx import React from 'react' @@ -192,24 +192,24 @@ export default () => {
{ - 提交 + submit ) diff --git a/docs/guide/advanced/destructor.zh-CN.md b/docs/guide/advanced/destructor.zh-CN.md new file mode 100644 index 00000000000..7b627d966a6 --- /dev/null +++ b/docs/guide/advanced/destructor.zh-CN.md @@ -0,0 +1,233 @@ +# 前后端数据差异兼容方案 + +很多时候,我们总会遇到前端数据结构与后端数据结构不匹配的场景,看似很简单的问题,其实解决起来非常的让人难受,最常见的问题就是: + +前端日期范围组件输出的是数组结构,但是后端要求的格式是拆分扁平数据结构,这种问题很大程度是受后端领域模型所限制,因为从后端模型设计的角度来看,拆分扁平结构是最佳方案; + +但从前端组件化角度来看,数组结构又是最佳的; + +所以哪一边都有其道理,可惜的是,每次都只能前端取消化这样一个不平等条约,不过,有了 Formily,你就完全不需要为这样一个尴尬局面而难受了,**Formily 提供了解构路径的能力,可以帮助用户快速解决这类问题。**,下面可以看看例子 + +## Markup Schema 案例 + +```tsx +import React from 'react' +import { + Form, + FormItem, + DatePicker, + FormButtonGroup, + Radio, + Submit, +} from '@formily/antd' +import { createForm, onFieldValueChange } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import 'antd/lib/alert/style' + +const SchemaField = createSchemaField({ + components: { + FormItem, + DatePicker, + Radio, + }, +}) + +const form = createForm({ + effects() { + onFieldValueChange('visible_destructor', (field) => { + form.setFieldState('[startDate,endDate]', (state) => { + state.visible = !!field.value + }) + }) + }, +}) + +export default () => { + return ( +
+ + + + + + +
+          
+            {(form) => JSON.stringify(form.values, null, 2)}
+          
+        
+
+ + 提交 + +
+ ) +} +``` + +## JSON Schema 案例 + +```tsx +import React from 'react' +import { + Form, + FormItem, + DatePicker, + FormButtonGroup, + Radio, + Submit, +} from '@formily/antd' +import { createForm } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import 'antd/lib/alert/style' + +const SchemaField = createSchemaField({ + components: { + FormItem, + DatePicker, + Radio, + }, +}) + +const form = createForm() + +const schema = { + type: 'object', + properties: { + visible_destructor: { + type: 'boolean', + title: '是否显示解构字段', + default: true, + enum: [ + { label: '是', value: true }, + { label: '否', value: false }, + ], + 'x-decorator': 'FormItem', + 'x-component': 'Radio.Group', + }, + undestructor: { + type: 'string', + title: '解构前', + 'x-decorator': 'FormItem', + 'x-component': 'DatePicker.RangePicker', + }, + '[startDate,endDate]': { + type: 'string', + title: '解构后', + default: ['2020-11-20', '2021-12-30'], + 'x-decorator': 'FormItem', + 'x-component': 'DatePicker.RangePicker', + 'x-reactions': { + dependencies: ['visible_destructor'], + fulfill: { + state: { + visible: '{{!!$deps[0]}}', + }, + }, + }, + }, + }, +} + +export default () => { + return ( +
+ + +
+          
+            {(form) => JSON.stringify(form.values, null, 2)}
+          
+        
+
+ + 提交 + + + ) +} +``` + +## 纯 JSX 案例 + +```tsx +import React from 'react' +import { + Form, + FormItem, + DatePicker, + FormButtonGroup, + Radio, + Submit, +} from '@formily/antd' +import { createForm } from '@formily/core' +import { Field, FormConsumer } from '@formily/react' +import 'antd/lib/alert/style' + +const form = createForm() + +export default () => { + return ( +
+ + + { + field.visible = !!field.query('visible_destructor').value() + }} + /> + +
+          
+            {(form) => JSON.stringify(form.values, null, 2)}
+          
+        
+
+ + 提交 + + + ) +} +``` diff --git a/docs/guide/advanced/layout.md b/docs/guide/advanced/layout.md index 014c754a126..0f062b76b54 100644 --- a/docs/guide/advanced/layout.md +++ b/docs/guide/advanced/layout.md @@ -1,10 +1,10 @@ -# 实现表单布局 +# Form Layout -表单布局主要是使用[@formily/antd](https://antd.formilyjs.org) 或 [@formily/next](https://next.formilyjs.org) 中的: +The form layout mainly uses [@formily/antd](https://antd.formilyjs.org) or [@formily/next](https://next.formilyjs.org): -- [FormLayout](https://antd.formilyjs.org/components/form-layout) 组件 -- [FormItem](https://antd.formilyjs.org/components/form-item) 组件 -- [FormGrid](https://antd.formilyjs.org/components/form-grid) 组件 -- [Space](https://antd.formilyjs.org/components/space) 组件 +- [FormLayout](http://antd.formilyjs.org/components/form-layout) Component +- [FormItem](http://antd.formilyjs.org/components/form-item) Component +- [FormGrid](http://antd.formilyjs.org/components/form-grid) Component +- [Space](http://antd.formilyjs.org/components/space) Component -这 4 个组件基本上能解决所有复杂表单布局场景,我们只需要灵活的组合使用这几个组件即可。 +These 4 components can basically solve all complex form layout scenarios, we only need to flexibly combine these components. diff --git a/docs/guide/advanced/layout.zh-CN.md b/docs/guide/advanced/layout.zh-CN.md new file mode 100644 index 00000000000..014c754a126 --- /dev/null +++ b/docs/guide/advanced/layout.zh-CN.md @@ -0,0 +1,10 @@ +# 实现表单布局 + +表单布局主要是使用[@formily/antd](https://antd.formilyjs.org) 或 [@formily/next](https://next.formilyjs.org) 中的: + +- [FormLayout](https://antd.formilyjs.org/components/form-layout) 组件 +- [FormItem](https://antd.formilyjs.org/components/form-item) 组件 +- [FormGrid](https://antd.formilyjs.org/components/form-grid) 组件 +- [Space](https://antd.formilyjs.org/components/space) 组件 + +这 4 个组件基本上能解决所有复杂表单布局场景,我们只需要灵活的组合使用这几个组件即可。 diff --git a/docs/guide/advanced/linkages.md b/docs/guide/advanced/linkages.md index 020cd13bdac..aec8e499874 100644 --- a/docs/guide/advanced/linkages.md +++ b/docs/guide/advanced/linkages.md @@ -1,10 +1,11 @@ -# 实现联动逻辑 +# Linkage Logic -Formily1.x 中实现联动逻辑只有一种模式,也就是主动模式,必须要监听一个或多个字段的事件变化去控制另一个或者多个字段的状态,这样对于一对多联动场景很方便,但是对于多对一场景就很麻烦了,需要监听多个字段的变化去控制一个字段状态,所以 Formily2.x 提供了响应式机制,可以让联动支持被动式联动,只需要关注某个字段所依赖的字段即可,依赖字段变化了,被依赖的字段即可自动联动。 +There is only one mode to realize linkage logic in Formily 1.x, that is, active mode. It is necessary to monitor the event changes of one or more fields to control the state of another or more fields. +This is very convenient for one-to-many linkage scenarios, but it is very troublesome for many-to-one scenarios. It is necessary to monitor the changes of multiple fields to control the state of a field. Therefore, Formily 2.x provides a responsive mechanism that allows the linkage to support passive linkage. You only need to pay attention to the field that a field depends on. When the dependent field changes, the dependent field can be automatically linked. -## 主动模式 +## Active Mode -主动联动核心是基于 +The core of active linkage is based on - [FormEffectHooks](https://core.formilyjs.org/api/entry/form-effect-hooks) - [FieldEffectHooks](https://core.formilyjs.org/api/entry/field-effect-hooks) @@ -12,11 +13,11 @@ Formily1.x 中实现联动逻辑只有一种模式,也就是主动模式,必 - [setFieldState](https://core.formilyjs.org/api/models/form#setfieldstate) - [SchemaReactions](https://react.formilyjs.org/api/shared/schema#schemareactions) -实现主动联动,优点是实现一对多联动时非常方便 +Realize active linkage, the advantage is that it is very convenient to realize one-to-many linkage. -### 一对一联动 +### One-to-One Linkage -#### Effects 用例 +#### Effects Use Cases ```tsx import React from 'react' @@ -28,7 +29,7 @@ const form = createForm({ effects() { onFieldValueChange('select', (field) => { form.setFieldState('input', (state) => { - //对于初始联动,如果字段找不到,setFieldState会将更新推入更新队列,直到字段出现再执行操作 + //For the initial linkage, if the field cannot be found, setFieldState will push the update into the update queue until the field appears before performing the operation state.display = field.value }) }) @@ -48,19 +49,19 @@ export default () => ( @@ -76,11 +77,11 @@ export default () => ( ) ``` -#### SchemaReactions 用例 +#### SchemaReactions Use Cases ```tsx import React from 'react' -import { createForm, onFieldValueChange } from '@formily/core' +import { createForm } from '@formily/core' import { createSchemaField, FormConsumer } from '@formily/react' import { Form, FormItem, Input, Select } from '@formily/antd' @@ -99,12 +100,12 @@ export default () => ( ( /> @@ -135,9 +136,9 @@ export default () => ( ) ``` -### 一对多联动 +### One-to-Many Linkage -#### Effects 用例 +#### Effects Use Cases ```tsx import React from 'react' @@ -149,7 +150,7 @@ const form = createForm({ effects() { onFieldValueChange('select', (field) => { form.setFieldState('*(input1,input2)', (state) => { - //对于初始联动,如果字段找不到,setFieldState会将更新推入更新队列,直到字段出现再执行操作 + //For the initial linkage, if the field cannot be found, setFieldState will push the update into the update queue until the field appears before performing the operation state.display = field.value }) }) @@ -169,25 +170,25 @@ export default () => ( @@ -203,11 +204,11 @@ export default () => ( ) ``` -#### SchemaReactions 用例 +#### SchemaReactions Use Cases ```tsx import React from 'react' -import { createForm, onFieldValueChange } from '@formily/core' +import { createForm } from '@formily/core' import { createSchemaField, FormConsumer } from '@formily/react' import { Form, FormItem, Input, Select } from '@formily/antd' @@ -226,12 +227,12 @@ export default () => ( ( /> @@ -268,9 +269,9 @@ export default () => ( ) ``` -### 依赖联动 +### Rely on Linkage -#### Effects 用例 +#### Effects Use Cases ```tsx import React from 'react' @@ -310,21 +311,21 @@ export default () => ( ( ) ``` -#### SchemaReactions 用例 +#### SchemaReactions Use Cases ```tsx import React from 'react' -import { createForm, onFieldValueChange } from '@formily/core' +import { createForm } from '@formily/core' import { createSchemaField, FormConsumer } from '@formily/react' import { Form, FormItem, Input, NumberPicker } from '@formily/antd' @@ -364,7 +365,7 @@ export default () => ( ( /> ( /> ( ) ``` -### 链式联动 +### Chain Linkage -#### Effects 用例 +#### Effects Use Cases ```tsx import React from 'react' @@ -427,13 +428,13 @@ const form = createForm({ effects() { onFieldValueChange('select', (field) => { form.setFieldState('input1', (state) => { - //对于初始联动,如果字段找不到,setFieldState会将更新推入更新队列,直到字段出现再执行操作 + //For the initial linkage, if the field cannot be found, setFieldState will push the update into the update queue until the field appears before performing the operation state.visible = !!field.value }) }) onFieldValueChange('input1', (field) => { form.setFieldState('input2', (state) => { - //对于初始联动,如果字段找不到,setFieldState会将更新推入更新队列,直到字段出现再执行操作 + //For the initial linkage, if the field cannot be found, setFieldState will push the update into the update queue until the field appears before performing the operation state.visible = !!field.value }) }) @@ -453,29 +454,29 @@ export default () => ( @@ -491,11 +492,11 @@ export default () => ( ) ``` -#### SchemaReactions 用例 +#### SchemaReactions Use Cases ```tsx import React from 'react' -import { createForm, onFieldValueChange } from '@formily/core' +import { createForm } from '@formily/core' import { createSchemaField, FormConsumer } from '@formily/react' import { Form, FormItem, Input, Select } from '@formily/antd' @@ -514,11 +515,11 @@ export default () => ( ( /> ( /> @@ -568,9 +569,9 @@ export default () => ( ) ``` -### 循环联动 +### Loop Linkage -#### Effects 用例 +#### Effects Use Cases ```tsx import React from 'react' @@ -622,19 +623,19 @@ export default () => ( @@ -650,14 +651,13 @@ export default () => ( ) ``` -#### SchemaReactions 用例 +#### SchemaReactions Use Cases ```tsx import React from 'react' -import { createForm, onFieldInputValueChange, onFieldInit } from '@formily/core' +import { createForm } from '@formily/core' import { createSchemaField, FormConsumer } from '@formily/react' import { Form, FormItem, NumberPicker } from '@formily/antd' -import { untracked } from '@formily/reactive' const form = createForm() const SchemaField = createSchemaField({ @@ -672,7 +672,7 @@ export default () => ( ( /> ( /> ( ) ``` -### 自身联动 +### Self Linkage -#### Effects 用例 +#### Effects Use Cases ```tsx import React from 'react' @@ -780,7 +780,7 @@ export default () => ( @@ -796,11 +796,11 @@ export default () => ( ) ``` -#### SchemaReactions 用例 +#### SchemaReactions Use Cases ```tsx import React from 'react' -import { createForm, onFieldValueChange } from '@formily/core' +import { createForm } from '@formily/core' import { createSchemaField, FormConsumer } from '@formily/react' import { Form, FormItem, Input } from '@formily/antd' import './input.less' @@ -820,7 +820,7 @@ export default () => ( ( state: { 'component[1].style.backgroundColor': '{{$self.value}}', }, - //以下用法也可以 + //The following usage is also possible // schema: { // 'x-component-props.style.backgroundColor': '{{$self.value}}', // }, @@ -848,9 +848,9 @@ export default () => ( ) ``` -### 异步联动 +### Asynchronous Linkage -#### Effects 用例 +#### Effects Use Cases ```tsx import React from 'react' @@ -865,7 +865,7 @@ const form = createForm({ setTimeout(() => { field.loading = false form.setFieldState('input', (state) => { - //对于初始联动,如果字段找不到,setFieldState会将更新推入更新队列,直到字段出现再执行操作 + //For the initial linkage, if the field cannot be found, setFieldState will push the update into the update queue until the field appears before performing the operation state.display = field.value }) }, 1000) @@ -886,19 +886,19 @@ export default () => ( ( ) ``` -#### SchemaReactions 用例 +#### SchemaReactions Use Cases ```tsx import React from 'react' -import { createForm, onFieldValueChange } from '@formily/core' +import { createForm } from '@formily/core' import { createSchemaField, FormConsumer } from '@formily/react' import { Form, FormItem, Input, Select } from '@formily/antd' @@ -937,7 +937,7 @@ const SchemaField = createSchemaField({ setTimeout(() => { field.loading = false form.setFieldState('input', (state) => { - //对于初始联动,如果字段找不到,setFieldState会将更新推入更新队列,直到字段出现再执行操作 + //For the initial linkage, if the field cannot be found, setFieldState will push the update into the update queue until the field appears before performing the operation state.display = field.value }) }, 1000) @@ -950,12 +950,12 @@ export default () => ( ( /> ( ) ``` -## 被动模式 +## Passive Mode -被动模式的核心是基于 +The core of the passive mode is based on -- [onFieldReact](https://core.formilyjs.org/api/entry/field-effect-hooks#onfieldreact)实现全局响应式逻辑 -- [FieldReaction](https://core.formilyjs.org/api/models/field#fieldreaction)实现局部响应式逻辑 -- [SchemaReactions](https://react.formilyjs.org/api/shared/schema#schemareactions)实现 Schema 协议中的结构化逻辑描述(内部是基于 FieldReaction 来实现的) +- [onFieldReact](https://core.formilyjs.org/api/entry/field-effect-hooks#onfieldreact) Implement global reactive logic +- [FieldReaction](https://core.formilyjs.org/api/models/field#fieldreaction) Implement partial responsive logic +- [SchemaReactions](https://react.formilyjs.org/api/shared/schema#schemareactions) Implement the structured logical description in the Schema protocol (the internal implementation is based on FieldReaction) -### 一对一联动 +### One-to-One Linkage -#### Effects 用例 +#### Effects Use Cases ```tsx import React from 'react' @@ -1025,19 +1025,19 @@ export default () => ( @@ -1053,11 +1053,11 @@ export default () => ( ) ``` -#### SchemaReactions 用例 +#### SchemaReactions Use Cases ```tsx import React from 'react' -import { createForm, onFieldValueChange } from '@formily/core' +import { createForm } from '@formily/core' import { createSchemaField, FormConsumer } from '@formily/react' import { Form, FormItem, Input, Select } from '@formily/antd' @@ -1076,19 +1076,19 @@ export default () => ( ( ) ``` -### 一对多联动 +### One-to-Many Linkage -#### Effects 用例 +#### Effects Use Cases ```tsx import React from 'react' @@ -1143,25 +1143,25 @@ export default () => ( @@ -1177,11 +1177,11 @@ export default () => ( ) ``` -#### SchemaReactions 用例 +#### SchemaReactions Use Cases ```tsx import React from 'react' -import { createForm, onFieldValueChange } from '@formily/core' +import { createForm } from '@formily/core' import { createSchemaField, FormConsumer } from '@formily/react' import { Form, FormItem, Input, Select } from '@formily/antd' @@ -1200,19 +1200,19 @@ export default () => ( ( /> ( ) ``` -### 依赖联动 +### Rely on Linkage -#### Effects 用例 +#### Effects Use Cases ```tsx import React from 'react' @@ -1281,21 +1281,21 @@ export default () => ( ( ) ``` -#### SchemaReactions 用例 +#### SchemaReactions Use Cases ```tsx import React from 'react' -import { createForm, onFieldValueChange } from '@formily/core' +import { createForm } from '@formily/core' import { createSchemaField, FormConsumer } from '@formily/react' import { Form, FormItem, Input, NumberPicker } from '@formily/antd' @@ -1335,21 +1335,21 @@ export default () => ( ( ) ``` -### 链式联动 +### Chain Linkage -#### Effects 用例 +#### Effects Use Cases ```tsx import React from 'react' @@ -1408,29 +1408,29 @@ export default () => ( @@ -1446,11 +1446,11 @@ export default () => ( ) ``` -#### SchemaReactions 用例 +#### SchemaReactions Use Cases ```tsx import React from 'react' -import { createForm, onFieldValueChange } from '@formily/core' +import { createForm } from '@formily/core' import { createSchemaField, FormConsumer } from '@formily/react' import { Form, FormItem, Input, Select } from '@formily/antd' @@ -1469,22 +1469,22 @@ export default () => ( ( /> ( ) ``` -### 循环联动 +### Loop Linkage -被动模式实现循环联动会有问题,因为被动模式感知到的数据变化会引发链式联动 +Passive mode will have problems to achieve cyclic linkage, because the data changes sensed by passive mode will trigger chain linkage. Chain linkage will cause mutually exclusive linkage that cannot break the cycle, so it is recommended to use active mode to achieve cyclic linkage -链式联动就会出现无法打破循环的互斥联动,所以推荐用主动模式实现循环联动 +### Self Linkage -### 自身联动 - -#### Effects 用例 +#### Effects Use Cases ```tsx import React from 'react' @@ -1565,7 +1563,7 @@ export default () => ( @@ -1581,11 +1579,11 @@ export default () => ( ) ``` -#### SchemaReactions 用例 +#### SchemaReactions Use Cases ```tsx import React from 'react' -import { createForm, onFieldValueChange } from '@formily/core' +import { createForm } from '@formily/core' import { createSchemaField, FormConsumer } from '@formily/react' import { Form, FormItem, Input } from '@formily/antd' import './input.less' @@ -1605,7 +1603,7 @@ export default () => ( ( state: { 'component[1].style.backgroundColor': '{{$self.value}}', }, - //以下用法也可以 + //The following usage is also possible // schema: { // 'x-component-props.style.backgroundColor': '{{$self.value}}', // }, @@ -1632,9 +1630,9 @@ export default () => ( ) ``` -### 异步联动 +### Asynchronous Linkage -#### Effects 用例 +#### Effects Use Cases ```tsx import React from 'react' @@ -1672,19 +1670,19 @@ export default () => ( ( ) ``` -#### SchemaReactions 用例 +#### SchemaReactions Use Cases ```tsx import React from 'react' -import { createForm, onFieldValueChange } from '@formily/core' +import { createForm } from '@formily/core' import { createSchemaField, FormConsumer } from '@formily/react' import { Form, FormItem, Input, Select } from '@formily/antd' @@ -1738,19 +1736,19 @@ export default () => ( { + form.setFieldState('input', (state) => { + //对于初始联动,如果字段找不到,setFieldState会将更新推入更新队列,直到字段出现再执行操作 + state.display = field.value + }) + }) + }, +}) + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + Select, + }, +}) + +export default () => ( +
+ + + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +#### SchemaReactions 用例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, Input, Select } from '@formily/antd' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + Select, + }, +}) + +export default () => ( +
+ + + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +### 一对多联动 + +#### Effects 用例 + +```tsx +import React from 'react' +import { createForm, onFieldValueChange } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, Input, Select } from '@formily/antd' + +const form = createForm({ + effects() { + onFieldValueChange('select', (field) => { + form.setFieldState('*(input1,input2)', (state) => { + //对于初始联动,如果字段找不到,setFieldState会将更新推入更新队列,直到字段出现再执行操作 + state.display = field.value + }) + }) + }, +}) + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + Select, + }, +}) + +export default () => ( +
+ + + + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +#### SchemaReactions 用例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, Input, Select } from '@formily/antd' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + Select, + }, +}) + +export default () => ( +
+ + + + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +### 依赖联动 + +#### Effects 用例 + +```tsx +import React from 'react' +import { createForm, onFieldValueChange } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, Input, NumberPicker } from '@formily/antd' + +const form = createForm({ + effects() { + onFieldValueChange('dim_1', (field) => { + const dim1 = field.value + const dim2 = field.query('dim_2').value() + form.setFieldState('result', (state) => { + state.value = dim1 * dim2 + }) + }) + onFieldValueChange('dim_2', (field) => { + const dim1 = field.query('dim_1').value() + const dim2 = field.value || 0 + form.setFieldState('result', (state) => { + state.value = dim1 * dim2 + }) + }) + }, +}) + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + NumberPicker, + }, +}) + +export default () => ( +
+ + + + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +#### SchemaReactions 用例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, Input, NumberPicker } from '@formily/antd' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + NumberPicker, + }, +}) + +export default () => ( +
+ + + + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +### 链式联动 + +#### Effects 用例 + +```tsx +import React from 'react' +import { createForm, onFieldValueChange } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, Input, Select } from '@formily/antd' + +const form = createForm({ + effects() { + onFieldValueChange('select', (field) => { + form.setFieldState('input1', (state) => { + //对于初始联动,如果字段找不到,setFieldState会将更新推入更新队列,直到字段出现再执行操作 + state.visible = !!field.value + }) + }) + onFieldValueChange('input1', (field) => { + form.setFieldState('input2', (state) => { + //对于初始联动,如果字段找不到,setFieldState会将更新推入更新队列,直到字段出现再执行操作 + state.visible = !!field.value + }) + }) + }, +}) + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + Select, + }, +}) + +export default () => ( +
+ + + + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +#### SchemaReactions 用例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, Input, Select } from '@formily/antd' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + Select, + }, +}) + +export default () => ( +
+ + + + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +### 循环联动 + +#### Effects 用例 + +```tsx +import React from 'react' +import { createForm, onFieldInputValueChange } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, NumberPicker } from '@formily/antd' + +const form = createForm({ + effects() { + onFieldInputValueChange('total', (field) => { + if (field.value === undefined) return + form.setFieldState('count', (state) => { + const price = form.values.price + if (!price) return + state.value = field.value / price + }) + form.setFieldState('price', (state) => { + const count = form.values.count + if (!count) return + state.value = field.value / count + }) + }) + onFieldInputValueChange('price', (field) => { + form.setFieldState('total', (state) => { + const count = form.values.count + if (count === undefined) return + state.value = field.value * count + }) + }) + onFieldInputValueChange('count', (field) => { + form.setFieldState('total', (state) => { + const price = form.values.price + if (price === undefined) return + state.value = field.value * price + }) + }) + }, +}) + +const SchemaField = createSchemaField({ + components: { + FormItem, + NumberPicker, + }, +}) + +export default () => ( +
+ + + + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +#### SchemaReactions 用例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, NumberPicker } from '@formily/antd' +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + FormItem, + NumberPicker, + }, +}) + +export default () => ( +
+ + + + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +### 自身联动 + +#### Effects 用例 + +```tsx +import React from 'react' +import { createForm, onFieldValueChange } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, Input } from '@formily/antd' +import './input.less' + +const form = createForm({ + effects() { + onFieldValueChange('color', (field) => { + field.setComponentProps({ + style: { + backgroundColor: field.value, + }, + }) + }) + }, +}) + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + }, +}) + +export default () => ( +
+ + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +#### SchemaReactions 用例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, Input } from '@formily/antd' +import './input.less' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + }, +}) + +export default () => ( +
+ + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +### 异步联动 + +#### Effects 用例 + +```tsx +import React from 'react' +import { createForm, onFieldValueChange } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, Input, Select } from '@formily/antd' + +const form = createForm({ + effects() { + onFieldValueChange('select', (field) => { + field.loading = true + setTimeout(() => { + field.loading = false + form.setFieldState('input', (state) => { + //对于初始联动,如果字段找不到,setFieldState会将更新推入更新队列,直到字段出现再执行操作 + state.display = field.value + }) + }, 1000) + }) + }, +}) + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + Select, + }, +}) + +export default () => ( +
+ + + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +#### SchemaReactions 用例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, Input, Select } from '@formily/antd' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + Select, + }, + scope: { + asyncVisible(field) { + field.loading = true + setTimeout(() => { + field.loading = false + form.setFieldState('input', (state) => { + //对于初始联动,如果字段找不到,setFieldState会将更新推入更新队列,直到字段出现再执行操作 + state.display = field.value + }) + }, 1000) + }, + }, +}) + +export default () => ( +
+ + + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +## 被动模式 + +被动模式的核心是基于 + +- [onFieldReact](https://core.formilyjs.org/api/entry/field-effect-hooks#onfieldreact)实现全局响应式逻辑 +- [FieldReaction](https://core.formilyjs.org/api/models/field#fieldreaction)实现局部响应式逻辑 +- [SchemaReactions](https://react.formilyjs.org/api/shared/schema#schemareactions)实现 Schema 协议中的结构化逻辑描述(内部是基于 FieldReaction 来实现的) + +### 一对一联动 + +#### Effects 用例 + +```tsx +import React from 'react' +import { createForm, onFieldReact } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, Input, Select } from '@formily/antd' + +const form = createForm({ + effects() { + onFieldReact('input', (field) => { + field.display = field.query('select').value() + }) + }, +}) + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + Select, + }, +}) + +export default () => ( +
+ + + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +#### SchemaReactions 用例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, Input, Select } from '@formily/antd' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + Select, + }, +}) + +export default () => ( +
+ + + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +### 一对多联动 + +#### Effects 用例 + +```tsx +import React from 'react' +import { createForm, onFieldReact } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, Input, Select } from '@formily/antd' + +const form = createForm({ + effects() { + onFieldReact('*(input1,input2)', (field) => { + field.display = field.query('select').value() + }) + }, +}) + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + Select, + }, +}) + +export default () => ( +
+ + + + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +#### SchemaReactions 用例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, Input, Select } from '@formily/antd' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + Select, + }, +}) + +export default () => ( +
+ + + + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +### 依赖联动 + +#### Effects 用例 + +```tsx +import React from 'react' +import { createForm, onFieldReact } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, Input, NumberPicker } from '@formily/antd' + +const form = createForm({ + effects() { + onFieldReact('result', (field) => { + field.value = field.query('dim_1').value() * field.query('dim_2').value() + }) + }, +}) + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + NumberPicker, + }, +}) + +export default () => ( +
+ + + + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +#### SchemaReactions 用例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, Input, NumberPicker } from '@formily/antd' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + NumberPicker, + }, +}) + +export default () => ( +
+ + + + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +### 链式联动 + +#### Effects 用例 + +```tsx +import React from 'react' +import { createForm, onFieldReact } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, Input, Select } from '@formily/antd' + +const form = createForm({ + effects() { + onFieldReact('input1', (field) => { + field.visible = !!field.query('select').value() + }) + onFieldReact('input2', (field) => { + field.visible = !!field.query('input1').value() + }) + }, +}) + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + Select, + }, +}) + +export default () => ( +
+ + + + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +#### SchemaReactions 用例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, Input, Select } from '@formily/antd' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + Select, + }, +}) + +export default () => ( +
+ + + + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +### 循环联动 + +被动模式实现循环联动会有问题,因为被动模式感知到的数据变化会引发链式联动 + +链式联动就会出现无法打破循环的互斥联动,所以推荐用主动模式实现循环联动 + +### 自身联动 + +#### Effects 用例 + +```tsx +import React from 'react' +import { createForm, onFieldReact } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, Input } from '@formily/antd' +import './input.less' + +const form = createForm({ + effects() { + onFieldReact('color', (field) => { + field.setComponentProps({ + style: { + backgroundColor: field.value, + }, + }) + }) + }, +}) + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + }, +}) + +export default () => ( +
+ + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +#### SchemaReactions 用例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, Input } from '@formily/antd' +import './input.less' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + }, +}) + +export default () => ( +
+ + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +### 异步联动 + +#### Effects 用例 + +```tsx +import React from 'react' +import { createForm, onFieldReact } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, Input, Select } from '@formily/antd' + +const form = createForm({ + effects() { + onFieldReact('input', (field) => { + const select = field.query('select').take() + if (!select) return + const selectValue = select.value + select.loading = true + if (selectValue) { + setTimeout(() => { + select.loading = false + field.display = selectValue + }, 1000) + } + }) + }, +}) + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + Select, + }, +}) + +export default () => ( +
+ + + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` + +#### SchemaReactions 用例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField, FormConsumer } from '@formily/react' +import { Form, FormItem, Input, Select } from '@formily/antd' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + Select, + }, + scope: { + asyncVisible(field) { + const select = field.query('select').take() + if(!select) return + const selectValue = select.value + select.loading = true + if (selectValue) { + setTimeout(() => { + select.loading = false + field.display = selectValue + }, 1000) + } + }, + }, +}) + +export default () => ( +
+ + + + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+
+) +``` diff --git a/docs/guide/advanced/validate.md b/docs/guide/advanced/validate.md index 00b9a1d4f17..a303b8bf5ed 100644 --- a/docs/guide/advanced/validate.md +++ b/docs/guide/advanced/validate.md @@ -1,17 +1,17 @@ -# 表单校验 +# Form Validation -Formily 的表单校验使用了极其强大且灵活的@formily/validator 校验引擎,校验主要分两种场景: +Formily's form validation uses the extremely powerful and flexible @formily/validator validation engine. There are two main scenarios for validation: -- Markup(JSON) Schema 场景协议校验属性校验,使用 JSON Schema 本身的校验属性与 x-validator 属性实现校验 -- 纯 JSX 场景校验属性,使用 validator 属性实现校验 +- Markup(JSON) Schema scene protocol verification property verification, using JSON Schema's own verification property and x-validator property to achieve verification +- Pure JSX scene verification properties, use validator property to achieve verification -同时我们还能在 effects 或者 x-reactions/reactions 中实现联动校验 +At the same time, we can also implement linkage verification in effects or x-reactions/reactions -具体规则校验文档参考 [FieldValidator](https://core.formilyjs.org/api/models/field#fieldvalidator) +Specific rule verification document reference [FieldValidator](https://core.formilyjs.org/api/models/field#fieldvalidator) -## 内置规则校验 +## Built-in rule check -#### Markup Schema 案例 +#### Markup Schema Use Cases ```tsx import React from 'react' @@ -34,63 +34,63 @@ export default () => ( ( ( ( ( ( ( ( ( ( ) ``` -#### JSON Schema 案例 +#### JSON Schema Use Cases ```tsx import React from 'react' @@ -257,7 +257,7 @@ const schema = { properties: { required_1: { name: 'required_1', - title: '必填', + title: 'Required', type: 'string', required: true, 'x-decorator': 'FormItem', @@ -265,7 +265,7 @@ const schema = { }, required_2: { name: 'required_2', - title: '必填', + title: 'Required', type: 'string', 'x-validator': { required: true, @@ -275,7 +275,7 @@ const schema = { }, required_3: { name: 'required_3', - title: '必填', + title: 'Required', type: 'string', 'x-validator': [ { @@ -287,7 +287,7 @@ const schema = { }, max_1: { name: 'max_1', - title: '最大值(>5报错)', + title: 'Maximum value (>5 error)', type: 'number', maximum: 5, 'x-decorator': 'FormItem', @@ -295,7 +295,7 @@ const schema = { }, max_2: { name: 'max_2', - title: '最大值(>5报错)', + title: 'Maximum value (>5 error)', type: 'number', 'x-validator': { maximum: 5, @@ -305,7 +305,7 @@ const schema = { }, max_3: { name: 'max_3', - title: '最大值(>5报错)', + title: 'Maximum value (>5 error)', type: 'number', 'x-validator': [ { @@ -317,7 +317,7 @@ const schema = { }, max_4: { name: 'max_4', - title: '最大值(>=5报错)', + title: 'Maximum value (>=5 error))', type: 'number', exclusiveMaximum: 5, 'x-decorator': 'FormItem', @@ -325,7 +325,7 @@ const schema = { }, max_5: { name: 'max_5', - title: '最大值(>=5报错)', + title: 'Maximum value (>=5 error))', type: 'number', 'x-validator': { exclusiveMaximum: 5, @@ -335,7 +335,7 @@ const schema = { }, max_6: { name: 'max_6', - title: '最大值(>=5报错)', + title: 'Maximum value (>=5 error))', type: 'number', 'x-validator': [ { @@ -347,7 +347,7 @@ const schema = { }, min_1: { name: 'min_1', - title: '最小值(<5报错)', + title: 'Minimum value (<5 error))', type: 'number', minimum: 5, 'x-decorator': 'FormItem', @@ -355,7 +355,7 @@ const schema = { }, min_2: { name: 'min_2', - title: '最小值(<5报错)', + title: 'Minimum value (<5 error))', type: 'number', 'x-validator': { minimum: 5, @@ -365,7 +365,7 @@ const schema = { }, min_3: { name: 'min_3', - title: '最小值(<5报错)', + title: 'Minimum value (<5 error))', type: 'string', 'x-validator': [ { @@ -377,7 +377,7 @@ const schema = { }, min_4: { name: 'min_4', - title: '最小值(<=5报错)', + title: 'Minimum value (<=5 error))', type: 'number', exclusiveMinimum: 5, 'x-decorator': 'FormItem', @@ -385,7 +385,7 @@ const schema = { }, min_5: { name: 'min_5', - title: '最小值(<=5报错)', + title: 'Minimum value (<=5 error))', type: 'number', 'x-validator': { exclusiveMinimum: 5, @@ -395,7 +395,7 @@ const schema = { }, min_6: { name: 'min_6', - title: '最小值(<=5报错)', + title: 'Minimum value (<=5 error))', type: 'number', 'x-validator': [ { @@ -407,7 +407,7 @@ const schema = { }, length_1: { name: 'length_1', - title: '长度为5', + title: 'Length is 5', type: 'string', 'x-validator': { len: 5, @@ -417,7 +417,7 @@ const schema = { }, length_2: { name: 'length_2', - title: '长度为5', + title: 'Length is 5', type: 'string', 'x-validator': [ { @@ -429,7 +429,7 @@ const schema = { }, maxlength_1: { name: 'maxlength_1', - title: '最大长度为5', + title: 'Maximum length is 5', type: 'string', maxLength: 5, 'x-decorator': 'FormItem', @@ -437,7 +437,7 @@ const schema = { }, maxlength_2: { name: 'maxlength_2', - title: '最大长度为5', + title: 'Maximum length is 5', type: 'string', 'x-validator': { max: 5, @@ -447,7 +447,7 @@ const schema = { }, maxlength_3: { name: 'maxlength_3', - title: '最大长度为5', + title: 'Maximum length is 5', type: 'string', 'x-validator': [ { @@ -459,7 +459,7 @@ const schema = { }, minlength_1: { name: 'minlength_1', - title: '最小长度为5', + title: 'Minimum length is 5', type: 'string', minLength: 5, 'x-decorator': 'FormItem', @@ -467,7 +467,7 @@ const schema = { }, minlength_2: { name: 'minlength_2', - title: '最小长度为5', + title: 'Minimum length is 5', type: 'string', 'x-validator': { min: 5, @@ -477,7 +477,7 @@ const schema = { }, minlength_3: { name: 'minlength_3', - title: '最小长度为5', + title: 'Minimum length is 5', type: 'string', 'x-validator': [ { @@ -489,7 +489,7 @@ const schema = { }, whitespace: { name: 'whitespace', - title: '排除纯空白字符', + title: 'Exclude pure whitespace characters', type: 'string', 'x-validator': [ { @@ -501,7 +501,7 @@ const schema = { }, enum: { name: 'enum', - title: '枚举匹配', + title: 'Enumeration match', type: 'string', 'x-validator': [ { @@ -513,7 +513,7 @@ const schema = { }, const: { name: 'const', - title: '常量匹配', + title: 'Constant match', type: 'string', const: '123', 'x-decorator': 'FormItem', @@ -521,7 +521,7 @@ const schema = { }, multipleOf: { name: 'multipleOf', - title: '整除匹配', + title: 'Divisible match', type: 'string', multipleOf: 2, 'x-decorator': 'FormItem', @@ -537,7 +537,7 @@ export default () => ( ) ``` -#### 纯 JSX 案例 +#### Pure JSX Case ```tsx import React from 'react' @@ -551,77 +551,77 @@ export default () => (
( ( ( ) ``` -## 内置格式校验 +## Built-in Format Verification -#### Markup Schema 案例 +#### Markup Schema Cases ```tsx import React, { Fragment } from 'react' @@ -705,7 +705,7 @@ const renderFormat = (format: string, key: number) => { { /> { /> { /> { /> ( ) ``` -#### JSON Schema 案例 +#### JSON Schema Cases ```tsx import React from 'react' @@ -804,7 +804,7 @@ const FORMATS = [ FORMATS.forEach((key) => { Object.assign(schema.properties, { [`${key}_1`]: { - title: `${key}格式`, + title: `${key} format`, type: 'string', required: true, format: key, @@ -812,7 +812,7 @@ FORMATS.forEach((key) => { 'x-component': 'Input', }, [`${key}_2`]: { - title: `${key}格式`, + title: `${key} format`, type: 'string', required: true, 'x-validator': key, @@ -820,7 +820,7 @@ FORMATS.forEach((key) => { 'x-component': 'Input', }, [`${key}_3`]: { - title: `${key}格式`, + title: `${key} format`, type: 'string', required: true, 'x-validator': { @@ -830,7 +830,7 @@ FORMATS.forEach((key) => { 'x-component': 'Input', }, [`${key}_4`]: { - title: `${key}格式`, + title: `${key} format`, type: 'string', required: true, 'x-validator': [key], @@ -839,7 +839,7 @@ FORMATS.forEach((key) => { }, [`${key}_5`]: { - title: `${key}格式`, + title: `${key} format`, type: 'string', required: true, 'x-validator': [ @@ -867,7 +867,7 @@ export default () => ( ) ``` -#### 纯 JSX 案例 +#### Pure JSX Cases ```tsx import React, { Fragment } from 'react' @@ -882,7 +882,7 @@ const renderFormat = (format: string, key: number) => { { /> { /> { /> ( ) ``` -## 自定义规则校验 +## Custom Rule Verification -#### Markup Schema 案例 +#### Markup Schema Cases ```tsx import React from 'react' @@ -962,7 +962,7 @@ const SchemaField = createSchemaField({ registerValidateRules({ global_1(value) { if (!value) return '' - return value !== '123' ? '错误了❎' : '' + return value !== '123' ? 'error❎' : '' }, global_2(value, rule) { if (!value) return '' @@ -977,17 +977,17 @@ registerValidateRules({ if (value < 10) { return { type: 'error', - message: '数值不能小于10', + message: 'The value cannot be less than 10', } } else if (value < 100) { return { type: 'warning', - message: '数值在100以内', + message: 'The value is within 100', } } else if (value < 1000) { return { type: 'success', - message: '数值大于100小于1000', + message: 'The value is greater than 100 and less than 1000', } } }, @@ -998,7 +998,7 @@ export default () => ( ( /> ( { if (!value) return '' - return value !== '123' ? '错误了❎' : '' + return value !== '123' ? 'error❎' : '' }} x-component="Input" x-decorator="FormItem" /> { if (!value) return '' if (value < 10) { return { type: 'error', - message: '数值不能小于10', + message: 'The value cannot be less than 10', } } else if (value < 100) { return { type: 'warning', - message: '数值在100以内', + message: 'The value is within 100', } } else if (value < 1000) { return { type: 'success', - message: '数值大于100小于1000', + message: 'The value is greater than 100 and less than 1000', } } }} @@ -1109,7 +1109,7 @@ export default () => ( ) ``` -#### JSON Schema 案例 +#### JSON Schema Cases ```tsx import React from 'react' @@ -1130,7 +1130,7 @@ const SchemaField = createSchemaField({ registerValidateRules({ global_1(value) { if (!value) return '' - return value !== '123' ? '错误了❎' : '' + return value !== '123' ? 'error❎' : '' }, global_2(value, rule) { if (!value) return '' @@ -1145,17 +1145,17 @@ registerValidateRules({ if (value < 10) { return { type: 'error', - message: '数值不能小于10', + message: 'The value cannot be less than 10', } } else if (value < 100) { return { type: 'warning', - message: '数值在100以内', + message: 'The value is within 100', } } else if (value < 1000) { return { type: 'success', - message: '数值大于100小于1000', + message: 'The value is greater than 100 and less than 1000', } } }, @@ -1165,7 +1165,7 @@ const schema = { type: 'object', properties: { global_style_1: { - title: '全局注册风格', + title: 'Global registration style', required: true, 'x-validator': { global_1: true, @@ -1174,27 +1174,27 @@ const schema = { 'x-decorator': 'FormItem', }, global_style_2: { - title: '全局注册风格', + title: 'Global registration style', required: true, 'x-validator': { global_2: true, - message: '错误了❎', + message: 'error❎', }, 'x-component': 'Input', 'x-decorator': 'FormItem', }, global_style_3: { - title: '全局注册风格', + title: 'Global registration style', required: true, 'x-validator': { global_3: true, - message: '错误了❎', + message: 'error❎', }, 'x-component': 'Input', 'x-decorator': 'FormItem', }, global_style_4: { - title: '全局注册风格', + title: 'Global registration style', required: true, 'x-validator': { global_4: true, @@ -1204,60 +1204,60 @@ const schema = { }, validator_style_1: { - title: '局部定义风格', + title: 'Locally defined style', required: true, 'x-validator': `{{(value)=> { if (!value) return '' - return value !== '123' ? '错误了❎' : '' + return value !== '123' ? 'error❎' : '' }}}`, 'x-component': 'Input', 'x-decorator': 'FormItem', }, validator_style_2: { - title: '局部定义风格', + title: 'Locally defined style', required: true, 'x-validator': { validator: `{{(value, rule)=> { if (!value) return '' return value !== '123' ? rule.message : '' }}}`, - message: '错误了❎', + message: 'error❎', }, 'x-component': 'Input', 'x-decorator': 'FormItem', }, validator_style_3: { - title: '局部定义风格', + title: 'Locally defined style', required: true, 'x-validator': { validator: `{{(value, rule)=> { if (!value) return '' return value === '123' }}}`, - message: '错误了❎', + message: 'error❎', }, 'x-component': 'Input', 'x-decorator': 'FormItem', }, validator_style_4: { - title: '局部定义风格', + title: 'Locally defined style', required: true, 'x-validator': `{{(value, rule)=> { if (!value) return '' if (value < 10) { return { type: 'error', - message: '数值不能小于10', + message: 'The value cannot be less than 10', } } else if (value < 100) { return { type: 'warning', - message: '数值在100以内', + message: 'The value is within 100', } } else if (value < 1000) { return { type: 'success', - message: '数值大于100小于1000', + message: 'The value is greater than 100 and less than 1000', } } }}}`, @@ -1274,7 +1274,7 @@ export default () => ( ) ``` -#### 纯 JSX 案例 +#### Pure JSX Cases ```tsx import React from 'react' @@ -1287,7 +1287,7 @@ const form = createForm() registerValidateRules({ global_1(value) { if (!value) return '' - return value !== '123' ? '错误了❎' : '' + return value !== '123' ? 'error❎' : '' }, global_2(value, rule) { if (!value) return '' @@ -1302,17 +1302,17 @@ registerValidateRules({ if (value < 10) { return { type: 'error', - message: '数值不能小于10', + message: 'The value cannot be less than 10', } } else if (value < 100) { return { type: 'warning', - message: '数值在100以内', + message: 'The value is within 100', } } else if (value < 1000) { return { type: 'success', - message: '数值大于100小于1000', + message: 'The value is greater than 100 and less than 1000', } } }, @@ -1322,7 +1322,7 @@ export default () => ( ( /> ( { if (!value) return '' - return value !== '123' ? '错误了❎' : '' + return value !== '123' ? 'error❎' : '' }} component={[Input]} decorator={[FormItem]} /> { if (!value) return '' if (value < 10) { return { type: 'error', - message: '数值不能小于10', + message: 'The value cannot be less than 10', } } else if (value < 100) { return { type: 'warning', - message: '数值在100以内', + message: 'The value is within 100', } } else if (value < 1000) { return { type: 'success', - message: '数值大于100小于1000', + message: 'The value is greater than 100 and less than 1000', } } }} @@ -1432,9 +1432,9 @@ export default () => ( ) ``` -## 自定义格式校验 +## Custom Format Verification -#### Markup Schema 案例 +#### Markup Schema Cases ```tsx import React from 'react' @@ -1460,18 +1460,18 @@ export default () => ( ( /> ( /> ( ( /> ( /> ( ) ``` -#### JSON Schema 案例 +#### JSON Schema Cases ```tsx import React from 'react' @@ -1565,69 +1565,69 @@ const schema = { type: 'object', properties: { global_style_1: { - title: '全局注册风格', + title: 'Global registration style', required: true, 'x-validator': { format: 'custom_format', - message: '错误❎', + message: 'error❎', }, 'x-component': 'Input', 'x-decorator': 'FormItem', }, global_style_2: { - title: '全局注册风格', + title: 'Global registration style', required: true, 'x-validator': 'custom_format', 'x-component': 'Input', 'x-decorator': 'FormItem', }, global_style_3: { - title: '全局注册风格', + title: 'Global registration style', required: true, 'x-validator': ['custom_format'], 'x-component': 'Input', 'x-decorator': 'FormItem', }, global_style_4: { - title: '全局注册风格', + title: 'Global registration style', required: true, 'x-validator': { format: 'custom_format', - message: '错误❎', + message: 'error❎', }, 'x-component': 'Input', 'x-decorator': 'FormItem', }, validator_style_1: { - title: '局部定义风格', + title: 'Locally defined style', required: true, pattern: /123/, 'x-component': 'Input', 'x-decorator': 'FormItem', }, validator_style_2: { - title: '局部定义风格', + title: 'Locally defined style', required: true, pattern: '123', 'x-component': 'Input', 'x-decorator': 'FormItem', }, validator_style_3: { - title: '局部定义风格', + title: 'Locally defined style', required: true, 'x-validator': { pattern: /123/, - message: '错误了❎', + message: 'error❎', }, 'x-component': 'Input', 'x-decorator': 'FormItem', }, validator_style_4: { - title: '局部定义风格', + title: 'Locally defined style', required: true, 'x-validator': { pattern: '123', - message: '错误了❎', + message: 'error❎', }, 'x-component': 'Input', 'x-decorator': 'FormItem', @@ -1642,7 +1642,7 @@ export default () => ( ) ``` -#### 纯 JSX 案例 +#### Pure JSX Cases ```tsx import React from 'react' @@ -1660,18 +1660,18 @@ export default () => ( ( /> ( /> ( ) ``` -## 异步校验 +## Asynchronous Verification -#### Markup Schema 案例 +#### Markup Schema Cases ```tsx import React from 'react' @@ -1746,7 +1746,7 @@ export default () => ( { return new Promise((resolve) => { @@ -1757,7 +1757,7 @@ export default () => ( if (value === '123') { resolve('') } else { - resolve('错误❎') + resolve('error❎') } }, 1000) }) @@ -1767,7 +1767,7 @@ export default () => ( /> ( if (value === '123') { resolve('') } else { - resolve('错误❎') + resolve('error❎') } }, 1000) }) @@ -1794,7 +1794,7 @@ export default () => ( ) ``` -#### JSON Schema 案例 +#### JSON Schema Cases ```tsx import React from 'react' @@ -1815,7 +1815,7 @@ const schema = { type: 'object', properties: { async_validate: { - title: '异步校验', + title: 'Asynchronous verification', required: true, 'x-validator': `{{(value) => { return new Promise((resolve) => { @@ -1826,7 +1826,7 @@ const schema = { if (value === '123') { resolve('') } else { - resolve('错误❎') + resolve('error❎') } }, 1000) }) @@ -1835,7 +1835,7 @@ const schema = { 'x-decorator': 'FormItem', }, async_validate_2: { - title: '异步校验(onBlur触发)', + title: 'Asynchronous verification (onBlur trigger)', required: true, 'x-validator': { triggerType: 'onBlur', @@ -1867,7 +1867,7 @@ export default () => ( ) ``` -#### 纯 JSX 案例 +#### Pure JSX Cases ```tsx import React from 'react' @@ -1881,7 +1881,7 @@ export default () => ( { return new Promise((resolve) => { @@ -1892,7 +1892,7 @@ export default () => ( if (value === '123') { resolve('') } else { - resolve('错误❎') + resolve('error❎') } }, 1000) }) @@ -1902,7 +1902,7 @@ export default () => ( /> ( if (value === '123') { resolve('') } else { - resolve('错误❎') + resolve('error ❎') } }, 1000) }) @@ -1928,9 +1928,9 @@ export default () => ( ) ``` -## 联动校验 +## Linkage Verification -#### Markup Schema 案例 +#### Markup Schema Cases ```tsx import React from 'react' @@ -1956,7 +1956,7 @@ export default () => ( required x-reactions={(field) => { field.errors = - field.query('bb').value() >= field.value ? 'AA必须大于BB' : '' + field.query('bb').value() >= field.value ? 'AA must be greater than BB' : '' }} x-component="NumberPicker" x-decorator="FormItem" @@ -1967,7 +1967,7 @@ export default () => ( required x-reactions={(field) => { field.errors = - field.query('aa').value() <= field.value ? 'AA必须大于BB' : '' + field.query('aa').value() <= field.value ? 'AA must be greater than BB' : '' }} x-component="NumberPicker" x-decorator="FormItem" @@ -1977,7 +1977,7 @@ export default () => ( ) ``` -#### JSON Schema 案例 +#### JSON Schema Cases ```tsx import React from 'react' @@ -2002,7 +2002,7 @@ const schema = { required: true, 'x-reactions': `{{(field) => { field.errors = - field.query('bb').value() >= field.value ? 'AA必须大于BB' : '' + field.query('bb').value() >= field.value ? 'AA must be greater than BB' : '' }}}`, 'x-component': 'NumberPicker', 'x-decorator': 'FormItem', @@ -2014,7 +2014,7 @@ const schema = { dependencies: ['aa'], fulfill: { state: { - errors: "{{$deps[0] <= $self.value ? 'AA必须大于BB' : ''}}", + errors: "{{$deps[0] <= $self.value ? 'AA must be greater than BB' : ''}}", }, }, }, @@ -2031,7 +2031,7 @@ export default () => ( ) ``` -#### 纯 JSX 案例 +#### Pure JSX Cases ```tsx import React from 'react' @@ -2049,7 +2049,7 @@ export default () => ( required reactions={(field) => { field.errors = - field.query('bb').value() >= field.value ? 'AA必须大于BB' : '' + field.query('bb').value() >= field.value ? 'AA must be greater than BB' : '' }} component={[NumberPicker]} decorator={[FormItem]} @@ -2060,7 +2060,7 @@ export default () => ( required reactions={(field) => { field.errors = - field.query('aa').value() <= field.value ? 'AA必须大于BB' : '' + field.query('aa').value() <= field.value ? 'AA must be greater than BB' : '' }} component={[NumberPicker]} decorator={[FormItem]} @@ -2069,9 +2069,9 @@ export default () => ( ) ``` -## 定制校验文案 +## Custom Verification Copy -主要通过[registerValidateLocale](https://core.formilyjs.org/api/entry/form-validator-registry#registervalidatelocale)来定制内置校验文案 +Mainly through [registerValidateLocale](https://core.formilyjs.org/api/entry/form-validator-registry#registervalidatelocale) to customize the built-in verification copy ```tsx import React from 'react' @@ -2090,7 +2090,7 @@ const SchemaField = createSchemaField({ registerValidateLocale({ zh: { - required: '定制的必填校验文案', + required: 'Custom required verification copy', }, }) diff --git a/docs/guide/advanced/validate.zh-CN.md b/docs/guide/advanced/validate.zh-CN.md new file mode 100644 index 00000000000..00b9a1d4f17 --- /dev/null +++ b/docs/guide/advanced/validate.zh-CN.md @@ -0,0 +1,2110 @@ +# 表单校验 + +Formily 的表单校验使用了极其强大且灵活的@formily/validator 校验引擎,校验主要分两种场景: + +- Markup(JSON) Schema 场景协议校验属性校验,使用 JSON Schema 本身的校验属性与 x-validator 属性实现校验 +- 纯 JSX 场景校验属性,使用 validator 属性实现校验 + +同时我们还能在 effects 或者 x-reactions/reactions 中实现联动校验 + +具体规则校验文档参考 [FieldValidator](https://core.formilyjs.org/api/models/field#fieldvalidator) + +## 内置规则校验 + +#### Markup Schema 案例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { Form, FormItem, Input, NumberPicker } from '@formily/antd' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + Input, + FormItem, + NumberPicker, + }, +}) + +export default () => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +) +``` + +#### JSON Schema 案例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { Form, FormItem, Input, NumberPicker } from '@formily/antd' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + Input, + FormItem, + NumberPicker, + }, +}) + +const schema = { + type: 'object', + properties: { + required_1: { + name: 'required_1', + title: '必填', + type: 'string', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + required_2: { + name: 'required_2', + title: '必填', + type: 'string', + 'x-validator': { + required: true, + }, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + required_3: { + name: 'required_3', + title: '必填', + type: 'string', + 'x-validator': [ + { + required: true, + }, + ], + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + max_1: { + name: 'max_1', + title: '最大值(>5报错)', + type: 'number', + maximum: 5, + 'x-decorator': 'FormItem', + 'x-component': 'NumberPicker', + }, + max_2: { + name: 'max_2', + title: '最大值(>5报错)', + type: 'number', + 'x-validator': { + maximum: 5, + }, + 'x-decorator': 'FormItem', + 'x-component': 'NumberPicker', + }, + max_3: { + name: 'max_3', + title: '最大值(>5报错)', + type: 'number', + 'x-validator': [ + { + maximum: 5, + }, + ], + 'x-decorator': 'FormItem', + 'x-component': 'NumberPicker', + }, + max_4: { + name: 'max_4', + title: '最大值(>=5报错)', + type: 'number', + exclusiveMaximum: 5, + 'x-decorator': 'FormItem', + 'x-component': 'NumberPicker', + }, + max_5: { + name: 'max_5', + title: '最大值(>=5报错)', + type: 'number', + 'x-validator': { + exclusiveMaximum: 5, + }, + 'x-decorator': 'FormItem', + 'x-component': 'NumberPicker', + }, + max_6: { + name: 'max_6', + title: '最大值(>=5报错)', + type: 'number', + 'x-validator': [ + { + exclusiveMaximum: 5, + }, + ], + 'x-decorator': 'FormItem', + 'x-component': 'NumberPicker', + }, + min_1: { + name: 'min_1', + title: '最小值(<5报错)', + type: 'number', + minimum: 5, + 'x-decorator': 'FormItem', + 'x-component': 'NumberPicker', + }, + min_2: { + name: 'min_2', + title: '最小值(<5报错)', + type: 'number', + 'x-validator': { + minimum: 5, + }, + 'x-decorator': 'FormItem', + 'x-component': 'NumberPicker', + }, + min_3: { + name: 'min_3', + title: '最小值(<5报错)', + type: 'string', + 'x-validator': [ + { + minimum: 5, + }, + ], + 'x-decorator': 'FormItem', + 'x-component': 'NumberPicker', + }, + min_4: { + name: 'min_4', + title: '最小值(<=5报错)', + type: 'number', + exclusiveMinimum: 5, + 'x-decorator': 'FormItem', + 'x-component': 'NumberPicker', + }, + min_5: { + name: 'min_5', + title: '最小值(<=5报错)', + type: 'number', + 'x-validator': { + exclusiveMinimum: 5, + }, + 'x-decorator': 'FormItem', + 'x-component': 'NumberPicker', + }, + min_6: { + name: 'min_6', + title: '最小值(<=5报错)', + type: 'number', + 'x-validator': [ + { + exclusiveMinimum: 5, + }, + ], + 'x-decorator': 'FormItem', + 'x-component': 'NumberPicker', + }, + length_1: { + name: 'length_1', + title: '长度为5', + type: 'string', + 'x-validator': { + len: 5, + }, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + length_2: { + name: 'length_2', + title: '长度为5', + type: 'string', + 'x-validator': [ + { + len: 5, + }, + ], + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + maxlength_1: { + name: 'maxlength_1', + title: '最大长度为5', + type: 'string', + maxLength: 5, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + maxlength_2: { + name: 'maxlength_2', + title: '最大长度为5', + type: 'string', + 'x-validator': { + max: 5, + }, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + maxlength_3: { + name: 'maxlength_3', + title: '最大长度为5', + type: 'string', + 'x-validator': [ + { + max: 5, + }, + ], + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + minlength_1: { + name: 'minlength_1', + title: '最小长度为5', + type: 'string', + minLength: 5, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + minlength_2: { + name: 'minlength_2', + title: '最小长度为5', + type: 'string', + 'x-validator': { + min: 5, + }, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + minlength_3: { + name: 'minlength_3', + title: '最小长度为5', + type: 'string', + 'x-validator': [ + { + min: 5, + }, + ], + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + whitespace: { + name: 'whitespace', + title: '排除纯空白字符', + type: 'string', + 'x-validator': [ + { + whitespace: true, + }, + ], + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + enum: { + name: 'enum', + title: '枚举匹配', + type: 'string', + 'x-validator': [ + { + enum: ['1', '2', '3'], + }, + ], + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + const: { + name: 'const', + title: '常量匹配', + type: 'string', + const: '123', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + multipleOf: { + name: 'multipleOf', + title: '整除匹配', + type: 'string', + multipleOf: 2, + 'x-decorator': 'FormItem', + 'x-component': 'NumberPicker', + }, + }, +} + +export default () => ( +
+ + +) +``` + +#### 纯 JSX 案例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { Field } from '@formily/react' +import { Form, FormItem, Input, NumberPicker } from '@formily/antd' + +const form = createForm() + +export default () => ( +
+ + + + + + + + + + + + + + + + + + + + + +) +``` + +## 内置格式校验 + +#### Markup Schema 案例 + +```tsx +import React, { Fragment } from 'react' +import { createForm } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { Form, FormItem, Input } from '@formily/antd' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + Input, + FormItem, + }, +}) + +const renderFormat = (format: string, key: number) => { + return ( + + + + + + + + ) +} + +const FORMATS = [ + 'url', + 'email', + 'phone', + 'ipv6', + 'ipv4', + 'number', + 'integer', + 'qq', + 'idcard', + 'money', + 'zh', + 'date', + 'zip', +] + +export default () => ( +
+ {FORMATS.map(renderFormat)} +
+) +``` + +#### JSON Schema 案例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { Form, FormItem, Input } from '@formily/antd' + +const form = createForm() + +const schema = { + type: 'object', + properties: {}, +} + +const FORMATS = [ + 'url', + 'email', + 'phone', + 'ipv6', + 'ipv4', + 'number', + 'integer', + 'qq', + 'idcard', + 'money', + 'zh', + 'date', + 'zip', +] + +FORMATS.forEach((key) => { + Object.assign(schema.properties, { + [`${key}_1`]: { + title: `${key}格式`, + type: 'string', + required: true, + format: key, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + [`${key}_2`]: { + title: `${key}格式`, + type: 'string', + required: true, + 'x-validator': key, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + [`${key}_3`]: { + title: `${key}格式`, + type: 'string', + required: true, + 'x-validator': { + format: key, + }, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + [`${key}_4`]: { + title: `${key}格式`, + type: 'string', + required: true, + 'x-validator': [key], + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + + [`${key}_5`]: { + title: `${key}格式`, + type: 'string', + required: true, + 'x-validator': [ + { + format: key, + }, + ], + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + }) +}) + +const SchemaField = createSchemaField({ + components: { + Input, + FormItem, + }, +}) + +export default () => ( +
+ + +) +``` + +#### 纯 JSX 案例 + +```tsx +import React, { Fragment } from 'react' +import { createForm } from '@formily/core' +import { Field } from '@formily/react' +import { Form, FormItem, Input } from '@formily/antd' + +const form = createForm() + +const renderFormat = (format: string, key: number) => { + return ( + + + + + + + ) +} + +const FORMATS = [ + 'url', + 'email', + 'phone', + 'ipv6', + 'ipv4', + 'number', + 'integer', + 'qq', + 'idcard', + 'money', + 'zh', + 'date', + 'zip', +] + +export default () => ( +
+ {FORMATS.map(renderFormat)} +
+) +``` + +## 自定义规则校验 + +#### Markup Schema 案例 + +```tsx +import React from 'react' +import { createForm, registerValidateRules } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { Form, FormItem, Input, NumberPicker } from '@formily/antd' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + Input, + FormItem, + NumberPicker, + }, +}) + +registerValidateRules({ + global_1(value) { + if (!value) return '' + return value !== '123' ? '错误了❎' : '' + }, + global_2(value, rule) { + if (!value) return '' + return value !== '123' ? rule.message : '' + }, + global_3(value, rule) { + if (!value) return '' + return value === '123' + }, + global_4(value, rule) { + if (!value) return '' + if (value < 10) { + return { + type: 'error', + message: '数值不能小于10', + } + } else if (value < 100) { + return { + type: 'warning', + message: '数值在100以内', + } + } else if (value < 1000) { + return { + type: 'success', + message: '数值大于100小于1000', + } + } + }, +}) + +export default () => ( +
+ + + + + + + { + if (!value) return '' + return value !== '123' ? '错误了❎' : '' + }} + x-component="Input" + x-decorator="FormItem" + /> + + + { + if (!value) return '' + if (value < 10) { + return { + type: 'error', + message: '数值不能小于10', + } + } else if (value < 100) { + return { + type: 'warning', + message: '数值在100以内', + } + } else if (value < 1000) { + return { + type: 'success', + message: '数值大于100小于1000', + } + } + }} + x-component="NumberPicker" + x-decorator="FormItem" + /> + +
+) +``` + +#### JSON Schema 案例 + +```tsx +import React from 'react' +import { createForm, registerValidateRules } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { Form, FormItem, Input, NumberPicker } from '@formily/antd' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + Input, + FormItem, + NumberPicker, + }, +}) + +registerValidateRules({ + global_1(value) { + if (!value) return '' + return value !== '123' ? '错误了❎' : '' + }, + global_2(value, rule) { + if (!value) return '' + return value !== '123' ? rule.message : '' + }, + global_3(value, rule) { + if (!value) return '' + return value === '123' + }, + global_4(value, rule) { + if (!value) return '' + if (value < 10) { + return { + type: 'error', + message: '数值不能小于10', + } + } else if (value < 100) { + return { + type: 'warning', + message: '数值在100以内', + } + } else if (value < 1000) { + return { + type: 'success', + message: '数值大于100小于1000', + } + } + }, +}) + +const schema = { + type: 'object', + properties: { + global_style_1: { + title: '全局注册风格', + required: true, + 'x-validator': { + global_1: true, + }, + 'x-component': 'Input', + 'x-decorator': 'FormItem', + }, + global_style_2: { + title: '全局注册风格', + required: true, + 'x-validator': { + global_2: true, + message: '错误了❎', + }, + 'x-component': 'Input', + 'x-decorator': 'FormItem', + }, + global_style_3: { + title: '全局注册风格', + required: true, + 'x-validator': { + global_3: true, + message: '错误了❎', + }, + 'x-component': 'Input', + 'x-decorator': 'FormItem', + }, + global_style_4: { + title: '全局注册风格', + required: true, + 'x-validator': { + global_4: true, + }, + 'x-component': 'Input', + 'x-decorator': 'FormItem', + }, + + validator_style_1: { + title: '局部定义风格', + required: true, + 'x-validator': `{{(value)=> { + if (!value) return '' + return value !== '123' ? '错误了❎' : '' + }}}`, + 'x-component': 'Input', + 'x-decorator': 'FormItem', + }, + validator_style_2: { + title: '局部定义风格', + required: true, + 'x-validator': { + validator: `{{(value, rule)=> { + if (!value) return '' + return value !== '123' ? rule.message : '' + }}}`, + message: '错误了❎', + }, + 'x-component': 'Input', + 'x-decorator': 'FormItem', + }, + validator_style_3: { + title: '局部定义风格', + required: true, + 'x-validator': { + validator: `{{(value, rule)=> { + if (!value) return '' + return value === '123' + }}}`, + message: '错误了❎', + }, + 'x-component': 'Input', + 'x-decorator': 'FormItem', + }, + validator_style_4: { + title: '局部定义风格', + required: true, + 'x-validator': `{{(value, rule)=> { + if (!value) return '' + if (value < 10) { + return { + type: 'error', + message: '数值不能小于10', + } + } else if (value < 100) { + return { + type: 'warning', + message: '数值在100以内', + } + } else if (value < 1000) { + return { + type: 'success', + message: '数值大于100小于1000', + } + } + }}}`, + 'x-component': 'Input', + 'x-decorator': 'FormItem', + }, + }, +} + +export default () => ( +
+ + +) +``` + +#### 纯 JSX 案例 + +```tsx +import React from 'react' +import { createForm, registerValidateRules } from '@formily/core' +import { Field } from '@formily/react' +import { Form, FormItem, Input, NumberPicker } from '@formily/antd' + +const form = createForm() + +registerValidateRules({ + global_1(value) { + if (!value) return '' + return value !== '123' ? '错误了❎' : '' + }, + global_2(value, rule) { + if (!value) return '' + return value !== '123' ? rule.message : '' + }, + global_3(value, rule) { + if (!value) return '' + return value === '123' + }, + global_4(value, rule) { + if (!value) return '' + if (value < 10) { + return { + type: 'error', + message: '数值不能小于10', + } + } else if (value < 100) { + return { + type: 'warning', + message: '数值在100以内', + } + } else if (value < 1000) { + return { + type: 'success', + message: '数值大于100小于1000', + } + } + }, +}) + +export default () => ( +
+ + + + + + { + if (!value) return '' + return value !== '123' ? '错误了❎' : '' + }} + component={[Input]} + decorator={[FormItem]} + /> + + + { + if (!value) return '' + if (value < 10) { + return { + type: 'error', + message: '数值不能小于10', + } + } else if (value < 100) { + return { + type: 'warning', + message: '数值在100以内', + } + } else if (value < 1000) { + return { + type: 'success', + message: '数值大于100小于1000', + } + } + }} + component={[NumberPicker]} + decorator={[FormItem]} + /> + +) +``` + +## 自定义格式校验 + +#### Markup Schema 案例 + +```tsx +import React from 'react' +import { createForm, registerValidateFormats } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { Form, FormItem, Input } from '@formily/antd' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + Input, + FormItem, + }, +}) + +registerValidateFormats({ + custom_format: /123/, +}) + +export default () => ( +
+ + + + + + + + + + + +
+) +``` + +#### JSON Schema 案例 + +```tsx +import React from 'react' +import { createForm, registerValidateFormats } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { Form, FormItem, Input } from '@formily/antd' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + Input, + FormItem, + }, +}) + +registerValidateFormats({ + custom_format: /123/, +}) + +const schema = { + type: 'object', + properties: { + global_style_1: { + title: '全局注册风格', + required: true, + 'x-validator': { + format: 'custom_format', + message: '错误❎', + }, + 'x-component': 'Input', + 'x-decorator': 'FormItem', + }, + global_style_2: { + title: '全局注册风格', + required: true, + 'x-validator': 'custom_format', + 'x-component': 'Input', + 'x-decorator': 'FormItem', + }, + global_style_3: { + title: '全局注册风格', + required: true, + 'x-validator': ['custom_format'], + 'x-component': 'Input', + 'x-decorator': 'FormItem', + }, + global_style_4: { + title: '全局注册风格', + required: true, + 'x-validator': { + format: 'custom_format', + message: '错误❎', + }, + 'x-component': 'Input', + 'x-decorator': 'FormItem', + }, + validator_style_1: { + title: '局部定义风格', + required: true, + pattern: /123/, + 'x-component': 'Input', + 'x-decorator': 'FormItem', + }, + validator_style_2: { + title: '局部定义风格', + required: true, + pattern: '123', + 'x-component': 'Input', + 'x-decorator': 'FormItem', + }, + validator_style_3: { + title: '局部定义风格', + required: true, + 'x-validator': { + pattern: /123/, + message: '错误了❎', + }, + 'x-component': 'Input', + 'x-decorator': 'FormItem', + }, + validator_style_4: { + title: '局部定义风格', + required: true, + 'x-validator': { + pattern: '123', + message: '错误了❎', + }, + 'x-component': 'Input', + 'x-decorator': 'FormItem', + }, + }, +} + +export default () => ( +
+ + +) +``` + +#### 纯 JSX 案例 + +```tsx +import React from 'react' +import { createForm, registerValidateFormats } from '@formily/core' +import { Field } from '@formily/react' +import { Form, FormItem, Input } from '@formily/antd' + +const form = createForm() + +registerValidateFormats({ + custom_format: /123/, +}) + +export default () => ( +
+ + + + + + + +) +``` + +## 异步校验 + +#### Markup Schema 案例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { Form, FormItem, Input } from '@formily/antd' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + Input, + FormItem, + }, +}) + +export default () => ( +
+ + { + return new Promise((resolve) => { + setTimeout(() => { + if (!value) { + resolve('') + } + if (value === '123') { + resolve('') + } else { + resolve('错误❎') + } + }, 1000) + }) + }} + x-component="Input" + x-decorator="FormItem" + /> + { + return new Promise((resolve) => { + setTimeout(() => { + if (!value) { + resolve('') + } + if (value === '123') { + resolve('') + } else { + resolve('错误❎') + } + }, 1000) + }) + }, + }} + x-component="Input" + x-decorator="FormItem" + /> + +
+) +``` + +#### JSON Schema 案例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { Form, FormItem, Input } from '@formily/antd' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + Input, + FormItem, + }, +}) + +const schema = { + type: 'object', + properties: { + async_validate: { + title: '异步校验', + required: true, + 'x-validator': `{{(value) => { + return new Promise((resolve) => { + setTimeout(() => { + if (!value) { + resolve('') + } + if (value === '123') { + resolve('') + } else { + resolve('错误❎') + } + }, 1000) + }) + }}}`, + 'x-component': 'Input', + 'x-decorator': 'FormItem', + }, + async_validate_2: { + title: '异步校验(onBlur触发)', + required: true, + 'x-validator': { + triggerType: 'onBlur', + validator: `{{(value) => { + return new Promise((resolve) => { + setTimeout(() => { + if (!value) { + resolve('') + } + if (value === '123') { + resolve('') + } else { + resolve('错误❎') + } + }, 1000) + }) + }}}`, + }, + 'x-component': 'Input', + 'x-decorator': 'FormItem', + }, + }, +} + +export default () => ( +
+ + +) +``` + +#### 纯 JSX 案例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { Field } from '@formily/react' +import { Form, FormItem, Input } from '@formily/antd' + +const form = createForm() + +export default () => ( +
+ { + return new Promise((resolve) => { + setTimeout(() => { + if (!value) { + resolve('') + } + if (value === '123') { + resolve('') + } else { + resolve('错误❎') + } + }, 1000) + }) + }} + component={[Input]} + decorator={[FormItem]} + /> + { + return new Promise((resolve) => { + setTimeout(() => { + if (!value) { + resolve('') + } + if (value === '123') { + resolve('') + } else { + resolve('错误❎') + } + }, 1000) + }) + }, + }} + component={[Input]} + decorator={[FormItem]} + /> + +) +``` + +## 联动校验 + +#### Markup Schema 案例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { Form, FormItem, NumberPicker } from '@formily/antd' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + NumberPicker, + FormItem, + }, +}) + +export default () => ( +
+ + { + field.errors = + field.query('bb').value() >= field.value ? 'AA必须大于BB' : '' + }} + x-component="NumberPicker" + x-decorator="FormItem" + /> + { + field.errors = + field.query('aa').value() <= field.value ? 'AA必须大于BB' : '' + }} + x-component="NumberPicker" + x-decorator="FormItem" + /> + +
+) +``` + +#### JSON Schema 案例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { Form, FormItem, NumberPicker } from '@formily/antd' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + NumberPicker, + FormItem, + }, +}) + +const schema = { + type: 'object', + properties: { + aa: { + title: 'AA', + required: true, + 'x-reactions': `{{(field) => { + field.errors = + field.query('bb').value() >= field.value ? 'AA必须大于BB' : '' + }}}`, + 'x-component': 'NumberPicker', + 'x-decorator': 'FormItem', + }, + bb: { + title: 'BB', + required: true, + 'x-reactions': { + dependencies: ['aa'], + fulfill: { + state: { + errors: "{{$deps[0] <= $self.value ? 'AA必须大于BB' : ''}}", + }, + }, + }, + 'x-component': 'NumberPicker', + 'x-decorator': 'FormItem', + }, + }, +} + +export default () => ( +
+ + +) +``` + +#### 纯 JSX 案例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { Field } from '@formily/react' +import { Form, FormItem, NumberPicker } from '@formily/antd' + +const form = createForm() + +export default () => ( +
+ { + field.errors = + field.query('bb').value() >= field.value ? 'AA必须大于BB' : '' + }} + component={[NumberPicker]} + decorator={[FormItem]} + /> + { + field.errors = + field.query('aa').value() <= field.value ? 'AA必须大于BB' : '' + }} + component={[NumberPicker]} + decorator={[FormItem]} + /> + +) +``` + +## 定制校验文案 + +主要通过[registerValidateLocale](https://core.formilyjs.org/api/entry/form-validator-registry#registervalidatelocale)来定制内置校验文案 + +```tsx +import React from 'react' +import { createForm, registerValidateLocale } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { Form, FormItem, Input } from '@formily/antd' + +const form = createForm() + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + }, +}) + +registerValidateLocale({ + zh: { + required: '定制的必填校验文案', + }, +}) + +export default () => ( +
+ + + +
+) +``` diff --git a/docs/guide/contribution.md b/docs/guide/contribution.md index 1a7b6d3307e..ded0ca9748a 100644 --- a/docs/guide/contribution.md +++ b/docs/guide/contribution.md @@ -1,109 +1,109 @@ -# 贡献指南 +# Contribution Guide -## 为什么要成为贡献者? +## Why become a contributor? -欢迎您来到我们的社区!**Formily** 是阿里巴巴唯一官方向外公布的开源表单框架,功能和质量都有一定保证,拥有众多的社区使用者,参与贡献可以使 **Formily** 变得强大,也会让更多开发者能够享受到更好的开发表单的体验,我们非常感谢任何对本项目发起 **Pull Request** 的同学。 +Welcome to our community!**Formily** It is the only official open-source form framework announced by Alibaba. Its functions and quality are guaranteed. It has a large number of community users. Participating in contributions can make **Formily** stronger and allow more developers to enjoy a better experience of developing forms. we are very grateful to any people who initiated **Pull Request** for this project. -## 我可以贡献什么? +## What can I contribute? -- features 新增/修改功能特性 -- unitest 新增/修改单测 -- bugfix 修复现有 issue 的问题 -- doc 文档改进 -- other 其他 +- Add&Update features +- Add/Update unit test cases +- Fix the existing issue +- Documentation improvements +- Other -## 如何贡献? +## How to contribute? -#### 拉取仓库 +#### Pull Repository -- 原始仓库:https://github.com/alibaba/formily -- 目标仓库:fork 到自己的 github 上 ![img](https://img.alicdn.com/tfs/TB1NLrjxXY7gK0jSZKzXXaikpXa-2206-490.png) +- Original repository: https://github.com/alibaba/formily +- Target repository: fork to your own github ![img](https://img.alicdn.com/tfs/TB1NLrjxXY7gK0jSZKzXXaikpXa-2206-490.png) -#### 拉取分支 +#### Pull Branch -原始分支是 alibaba/formily master,拉取后的分支应该是 quirkyshop/formily master +The original branch is alibaba/formily master, The branch after pulling should be quirkyshop/formily master -> 注意:建议分支名为[feat]-[name],[feat]是这个分支的类型,可选的有[feat][unitest][doc][bugfix][other],[name]则是名字,自定义就好了。eg. unittest-core(意为:对核心补充单测) +> Note: The recommended branch name is [feat]-[name], [feat] is the type of this branch. Featdoc[other] is optional, and [name] is the name, just customize it. eg. unittest-core (meaning: add single test to the core) -#### 提交代码 +#### Submit Code -代码风格遵循 2 空格,无分号,非说明请不要在代码中附带任何 console 相关的方法及 debugger。 开发完成后,到自己 fork 出来的仓库提交 pull request ![img](https://img.alicdn.com/tfs/TB1HSvqxkT2gK0jSZFkXXcIQFXa-2050-898.png)![img](https://img.alicdn.com/tfs/TB1O.6mxbr1gK0jSZR0XXbP8XXa-1696-254.png) +The code style follows 2 spaces and no semicolons. Please do not include any console-related methods and debuggers in the code unless it is explained. After the development is completed, submit a pull request to the repository you forked.![img](https://img.alicdn.com/tfs/TB1HSvqxkT2gK0jSZFkXXcIQFXa-2050-898.png)![img](https://img.alicdn.com/tfs/TB1O.6mxbr1gK0jSZR0XXbP8XXa-1696-254.png) -> 注意这里的左边目标仓库(base repository 是 alibaba/formily master) ,然后右边当前分支自己仓库的 doc-wiki +> Note the target repository on the left here(base repository is alibaba/formily master) . And then the doc-wiki of the current branch own repository on the right. -#### PR 规范 +#### PR Specification -参考文档:https://github.com/alibaba/formily/blob/master/.github/GIT_COMMIT_SPECIFIC.md +Reference documents: https://github.com/alibaba/formily/blob/master/.github/GIT_COMMIT_SPECIFIC.md -- PR 名称:格式:`(): ` 举例:`feat(core): add unit test` -- PR 内容:列举本次改动的内容 -- PR 要求:增加的 feat 内容,尽量做到注释清晰,相应的单测覆盖要尽可能覆盖 -- BUGFIX 要求:如果修改的问题和 issues 相关,请在内容中附上相关的 issueID。 +- PR name: format: `(): ` For example: `feat(core): add unit test` +- PR content: List the content of this change +- PR requirements: the added feat content, as far as possible, make clear comments. And the corresponding single test coverage should be covered as much 关注梁帅抽大奖 possible. +- BUGFIX requirements: If the modified issue is related to issues, please include the relevant issueID in the content. -#### 审核与合并 +#### Review&Merge -审核阶段会进入多 review 的流程,`@janryWang` 负责审核这个改动是否合并,其他同学也会参与讨论,讨论的经过都会留存在 github 的 PR 里,钉钉群也会收到相应的通知。 +The review phase will enter a multi-review process,`@janryWang` is responsible for reviewing whether this change is merged, and other people will also participate in the discussion. The discussion will be stored in the PR of github, and the DingTalk group will also receive corresponding notifications. -当看到 Pull requests 列表中的状态变为 Closed 即为合并成功。 ![img](https://img.alicdn.com/tfs/TB1HUnjxXY7gK0jSZKzXXaikpXa-964-104.png) +When you see that the status in the Pull requests list changes to Closed, the merge is successful. ![img](https://img.alicdn.com/tfs/TB1HUnjxXY7gK0jSZKzXXaikpXa-964-104.png) -#### 同步源仓库变更到 fork 后的仓库 +#### Synchronize source repository changes to repository after fork ``` -# 首先在自己的分支增加一个 upstream,即原仓库 +# First, add "upstream" to your branch, that is, the source repository $ git remote add upstream https://github.com/alibaba/formily.git -# 获取原仓库最新的变更 +# Get the latest changes to the source repository $ git fetch upstream -# 同步原仓库的改动到本地分支 -$ git pull upstream master [当前本地目标分支,不填默认就是当前分支] +# Synchronize the changes of the source repository to the local branch +$ git pull upstream master [The current local target branch, if not filled in, the current branch will be] ``` -#### 项目开发 +#### Project Development ```bash $ cd formily -$ yarn install # 安装整体项目依赖 -$ yarn build # 构建所有项目 -$ yarn test # 执行单元测试 +$ yarn install # Install overall project dependencies +$ yarn build # Build all projects +$ yarn test # Perform unit tests ``` -#### 开发文档 +#### Development Document -主项目文档 +Main project document ```bash $ yarn start ``` -内核项目文档 +Core project documentation ```bash $ yarn workspace @formily/core start ``` -React 项目文档 +React project documentation ```bash $ yarn workspace @formily/react start ``` -Vue 项目文档 +Vue project documentation ```bash $ yarn workspace @formily/vue start ``` -Antd 项目文档 +Antd project documentation ```bash $ yarn workspace @formily/antd start ``` -Fusion 项目文档 +Fusion project documentation ```bash $ yarn workspace @formily/next start ``` -Reactive 项目文档 +Reactive project documentation ```bash $ yarn workspace @formily/reactive start diff --git a/docs/guide/contribution.zh-CN.md b/docs/guide/contribution.zh-CN.md new file mode 100644 index 00000000000..1a7b6d3307e --- /dev/null +++ b/docs/guide/contribution.zh-CN.md @@ -0,0 +1,110 @@ +# 贡献指南 + +## 为什么要成为贡献者? + +欢迎您来到我们的社区!**Formily** 是阿里巴巴唯一官方向外公布的开源表单框架,功能和质量都有一定保证,拥有众多的社区使用者,参与贡献可以使 **Formily** 变得强大,也会让更多开发者能够享受到更好的开发表单的体验,我们非常感谢任何对本项目发起 **Pull Request** 的同学。 + +## 我可以贡献什么? + +- features 新增/修改功能特性 +- unitest 新增/修改单测 +- bugfix 修复现有 issue 的问题 +- doc 文档改进 +- other 其他 + +## 如何贡献? + +#### 拉取仓库 + +- 原始仓库:https://github.com/alibaba/formily +- 目标仓库:fork 到自己的 github 上 ![img](https://img.alicdn.com/tfs/TB1NLrjxXY7gK0jSZKzXXaikpXa-2206-490.png) + +#### 拉取分支 + +原始分支是 alibaba/formily master,拉取后的分支应该是 quirkyshop/formily master + +> 注意:建议分支名为[feat]-[name],[feat]是这个分支的类型,可选的有[feat][unitest][doc][bugfix][other],[name]则是名字,自定义就好了。eg. unittest-core(意为:对核心补充单测) + +#### 提交代码 + +代码风格遵循 2 空格,无分号,非说明请不要在代码中附带任何 console 相关的方法及 debugger。 开发完成后,到自己 fork 出来的仓库提交 pull request ![img](https://img.alicdn.com/tfs/TB1HSvqxkT2gK0jSZFkXXcIQFXa-2050-898.png)![img](https://img.alicdn.com/tfs/TB1O.6mxbr1gK0jSZR0XXbP8XXa-1696-254.png) + +> 注意这里的左边目标仓库(base repository 是 alibaba/formily master) ,然后右边当前分支自己仓库的 doc-wiki + +#### PR 规范 + +参考文档:https://github.com/alibaba/formily/blob/master/.github/GIT_COMMIT_SPECIFIC.md + +- PR 名称:格式:`(): ` 举例:`feat(core): add unit test` +- PR 内容:列举本次改动的内容 +- PR 要求:增加的 feat 内容,尽量做到注释清晰,相应的单测覆盖要尽可能覆盖 +- BUGFIX 要求:如果修改的问题和 issues 相关,请在内容中附上相关的 issueID。 + +#### 审核与合并 + +审核阶段会进入多 review 的流程,`@janryWang` 负责审核这个改动是否合并,其他同学也会参与讨论,讨论的经过都会留存在 github 的 PR 里,钉钉群也会收到相应的通知。 + +当看到 Pull requests 列表中的状态变为 Closed 即为合并成功。 ![img](https://img.alicdn.com/tfs/TB1HUnjxXY7gK0jSZKzXXaikpXa-964-104.png) + +#### 同步源仓库变更到 fork 后的仓库 + +``` +# 首先在自己的分支增加一个 upstream,即原仓库 +$ git remote add upstream https://github.com/alibaba/formily.git +# 获取原仓库最新的变更 +$ git fetch upstream +# 同步原仓库的改动到本地分支 +$ git pull upstream master [当前本地目标分支,不填默认就是当前分支] +``` + +#### 项目开发 + +```bash +$ cd formily +$ yarn install # 安装整体项目依赖 +$ yarn build # 构建所有项目 +$ yarn test # 执行单元测试 +``` + +#### 开发文档 + +主项目文档 + +```bash +$ yarn start +``` + +内核项目文档 + +```bash +$ yarn workspace @formily/core start +``` + +React 项目文档 + +```bash +$ yarn workspace @formily/react start +``` + +Vue 项目文档 + +```bash +$ yarn workspace @formily/vue start +``` + +Antd 项目文档 + +```bash +$ yarn workspace @formily/antd start +``` + +Fusion 项目文档 + +```bash +$ yarn workspace @formily/next start +``` +Reactive 项目文档 + +```bash +$ yarn workspace @formily/reactive start +``` \ No newline at end of file diff --git a/docs/guide/index.md b/docs/guide/index.md index 3dc89923c9b..0df40dd7343 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -1,55 +1,55 @@ -# 介绍 +# Introduction -## 问题 +## Problem -众所周知,表单场景一直都是前端中后台领域最复杂的场景,它的复杂度主要在哪里呢? +As we all know, the form scene has always been the most complex scene in the front-end and back-end fields. What is the main complexity of it? -- 字段数量多,如何让性能不随字段数量增加而变差? -- 字段关联逻辑复杂,如何更简单的实现复杂的联动逻辑?字段与字段关联时,如何保证不影响表单性能? +- There are a lot of fields, how can the performance not deteriorate with the increase of the number of fields? +- Field association logic is complex, how to implement complex linkage logic more simply? How to ensure that the form performance is not affected when the field is associated with the field? - - 一对多(异步) - - 多对一(异步) - - 多对多(异步) + - One-to-Many (asynchronous) + - Many-to-One (asynchronous) + - Many-to-Many (asynchronous) -- 表单数据管理复杂 - - 表单值转换逻辑复杂(前后端格式不一致) - - 同步默认值与异步默认值合并逻辑复杂 - - 跨表单数据通信,如何让性能不随字段数量增加而变差? -- 表单状态管理复杂 - - 着重提自增列表场景,如何让数组数据在移动,删除过程中,字段状态能够做到跟随移动? -- 表单的场景化复用 - - 查询列表 - - 弹窗/抽屉表单 - - 分步表单 - - 选项卡表单 -- 动态渲染述求很强烈 - - 字段配置化,让非专业前端也能快速搭建复杂表单 - - 跨端渲染,一份 JSON Schema,多端适配 - - 如何在表单协议中描述布局? - - 纵向布局 - - 横向布局 - - 网格布局 - - 弹性布局 - - 自由布局 - - 如何在表单协议中描述逻辑? +- Complex form data management + - Form value conversion logic is complex (front and back formats are inconsistent) + - The logic of merging synchronous and asynchronous default values is complicated + - Cross-form data communication, how to keep the performance from deteriorating with the increase in the number of fields? +- Complex form state management + - Focusing on the self-incrementing list scenario, how to make the array data move, and the field status can follow the move during the deletion process? +- Scene reuse of forms + - Query list + - Dialog/Drawer form + - Step form + - Tab form +- Dynamic rendering requirements are very strong + - Field configuration allows non-professional front-ends to quickly build complex forms + - Cross-terminal rendering, a JSON Schema, multi-terminal adaptation + - How to describe the layout in the form protocol? + - Vertical layout + - Horizontal layout + - Grid layout + - Flexible layout + - Free layout + - How to describe the logic in the form protocol? -这么多问题,怎么解决,想想就头大,但是我们还是得想办法解决,不仅要解决,还要优雅的解决,阿里数字供应链团队,在经历了大量的中后台实践和探索之后,总算沉淀出了 **Formily 表单解决方案** ,以上提到的所有问题,在经历了 UForm 到 Formily1.x,直到 Formily2.x 总算做到了 **优雅解决** 的程度。那 Formily2.x 是如何解决这些问题的呢? +So many problems, how to solve them, think about it, But we still have to find a solution,Not only to solve but also to solve elegantly, The Alibaba digital supply chain team, after experiencing a lot of middle and back-office practice and exploration, finally precipitated **Formily form solution**. All the problems mentioned above, after going through UForm to Formily1.x, until Formily2.x finally achieved the degree of **elegant solution**. So how does Formily 2.x solve these problems? -## 解法 +## Solution -为了解决以上问题,我们可以对问题做进一步提炼,得出可突破的方向。 +In order to solve the above problems, we can further refine the problem and come up with a breakthrough direction. -### 精确渲染 +### Accurate Rendering -在 React 场景下实现一个表单需求,因为要收集表单数据,实现一些联动需求,大多数都是通过 setState 来实现字段数据收集,这样实现非常简单,心智成本非常低,但是却又引入了性能问题,因为每次输入都会导致所有字段全量渲染,虽然在 DOM 更新层面是有 diff,但是 diff 也是有计算成本的,浪费了很多计算资源,如果用时间复杂度来看的话,初次渲染表单是 O(n),字段输入时也是 O(n),这样明显是不合理的。 +In the React scenario, to realize a form requirement, most of them use setState to realize field data collection. because form data needs to be collected and some linkage requirements are realized.This implementation is very simple and the mental cost is very low, but it also introduces performance problems, because each input will cause all fields to be rendered in full. Although there is diff at the DOM update level, diff also has a computational cost, which wastes a lot of computational resources. In terms of time complexity, the initial rendering of the form is O(n), and the field input is also O(n), which is obviously unreasonable. -历史的经验总是对人类有帮助的,几十年前,人类创造出了 MVVM 设计模式。这样的设计模式核心是将视图模型抽象出来,然后在 DSL 模板层消费,DSL 借助某种依赖收集机制,然后在视图模型中统一调度,保证每次输入都是精确渲染的,这就是工业级的 GUI 形态! +Historical experience is always helpful to mankind. Decades ago, humans created the MVVM design pattern. The core of this design pattern is to abstract the view model and consume it at the DSL template layer.SL uses a certain dependency collection mechanism, and then uniformly schedules in the view model to ensure that each input is accurately rendered. This is the industrial-grade GUI form! -刚好,github 社区为这样的 MVVM 模型抽象出了一个叫 [Mobx](https://github.com/mobxjs/mobx) 的状态管理解决方案,Mobx 最核心的能力就是它的依赖追踪机制和响应式模型的抽象能力。 +It just so happened that the github community abstracted a state management solution called Mobx for such MVVM models. The core capabilities of [Mobx](https://github.com/mobxjs/mobx) are its dependency tracking mechanism and the abstraction capabilities of responsive models. -所以,借助 Mobx,完全可以解决表单字段输入过程中的 O(n)问题,而且是可以很优雅的解决,但是 Formily2.x 在实现的过程中发现 Mobx 还是存在一些不兼容 Formily 核心思想的问题,最终,只能重新造了一个轮子,延续 Mobx 的核心思想的 [@formily/reactive](https://reactive.formilyjs.org) +Therefore, with the help of Mobx, the O(n) problem in the form field input process can be completely solved, and it can be solved very elegantly. However, during the implementation of Formily 2.x, it was discovered that Mobx still has some problems that are not compatible with Formily's core ideas. In the end, we only can reinvent one wheel,[@formily/reactive](https://reactive.formilyjs.org) which continues the core idea of Mobx. -这里提一下 [react-hook-form](https://github.com/react-hook-form/react-hook-form) ,非常流行,号称业界性能第一的表单方案,我们看看它最简单的案例: +Mention here [react-hook-form](https://github.com/react-hook-form/react-hook-form) , Very popular, known as the industry’s top performance form solution, let’s take a look at its simplest case: ```tsx pure import React from 'react' @@ -77,15 +77,15 @@ function App() { ReactDOM.render(, document.getElementById('root')) ``` -虽然值管理做到了精确渲染,但是在触发校验的时候,还是会导致表单全量渲染,因为 errors 状态的更新,是必须要整体受控渲染才能实现同步,这仅仅只是校验会全量渲染,其实还有联动,react-hook-form 要实现联动,同样是需要整体受控渲染才能实现联动。所以,如果要真正实现精确渲染,非 Reactive 不可! +Although the value management achieves accurate rendering, when the verification is triggered, the form will still be rendered in full. Because of the update of the errors state, the overall controlled rendering is necessary to achieve synchronization. This is only the full rendering of the verification meeting. In fact, there is linkage. To achieve linkage with react-hook-form, it also requires overall controlled rendering to achieve linkage. Therefore, if you want to truly achieve accurate rendering, it must be Reactive! -### 领域模型 +### Domain Model -前面问题中有提到表单的联动是非常复杂的,包含了字段间的各种关系,我们想象一下,大多数表单联动,基本上都是基于某些字段的值引发的联动,但是,实际业务需求可能会比较恶心,不仅要基于某些字段值引发联动,还会基于其他副作用值引发联动,比如应用状态,服务端数据状态,页面 URL,某个字段 UI 组件内部数据,当前字段自身的其他数据状态,某些特殊异步事件等等。用张图来描述: +As mentioned in the previous question, the linkage of forms is very complicated, including various relationships between fields. Let’s imagine that most form linkages are basically linkages triggered based on the values of certain fields. However, actual business requirements may be disgusting. It is not only necessary to trigger linkage based on certain field values, but also based on other side-effect values, such as application status, server data status, page URL, internal data of a UI component of a field, and current Other data status of the field itself, some special asynchronous events, etc. Use a picture to describe: ![image-20210202081316031](//img.alicdn.com/imgextra/i3/O1CN01LWjBSt251w5BtGHW2_!!6000000007467-55-tps-1100-432.svg) -从上图可以看到,想要达成一个联动关系,核心是将字段的某些状态属性与某些数据关联起来,这里的某些数据可以是外界数据,也可以是自身数据,比如字段的显示/隐藏与某些数据的关联,又比如字段的值与某些数据关联,还比如字段的禁用/编辑与某些数据关联,就举了 3 个例子,我们其实已经抽象出了一个最简单的 Field 模型: +As you can see from the above figure, in order to achieve a linkage relationship, the core is to associate certain state attributes of the field with certain data. Some data here can be external data or own data. For example, the display/hide of a field is associated with certain data, the value of a field is associated with certain data, and the disabling/editing of a field is associated with certain data. Here are three examples. We have actually abstracted it. One of the simplest Field model: ```typescript interface Field { @@ -95,9 +95,9 @@ interface Field { } ``` -当然,Field 模型仅仅只有这 3 个属性吗?肯定不是,如果我们要表达一个字段,那么字段的路径一定要有,因为要描述整个表单树结构,同时,我们还要管理起字段对应 UI 组件的属性,比如 Input 和 Select 都有它的属性,举个例子,Input 的 placeholder 与某些数据关联,或者 Select 的下拉选项与某些数据关联,这样就能理解了吧。所以,我们的 Field 模型可以是这样: +Of course, does the Field model only have these 3 attributes? Definitely not, if we want to express a field, then the path of the field must have, Because we want to describe the entire form tree structure, at the same time, we also need to manage the properties of the field corresponding to the UI component. For example, Input and Select have their properties. For example, the placeholder of Input is associated with some data, or the drop-down option of Select is associated with some data, so you can understand it. So, our Field model can look like this: -``` +```typescript interface Field { path:string[], value:any, @@ -107,67 +107,69 @@ interface Field { } ``` -我们加了 component 属性,它代表了字段所对应的 UI 组件和 UI 组件属性,这样就实现了某些数据与字段组件属性关联,甚至是与字段组件关联的能力。还有吗?当然还有,比如字段的外包裹容器,通常我们都叫 FormItem,它主要负责字段的外围的交互样式,比如字段标题,错误提示的样式等等,如果我们想要囊括更多联动,比如某些数据与 FormItem 的联动,那就得把外包裹容器也加进去。还有很多很多属性,这里没法一一列举。 +We have added the component attribute, which represents the UI component and UI component attribute corresponding to the field, so that the ability to associate certain data with the field component attribute, or even the field component, is realized. Are there any more? Of course, there are also, such as the outer package container of the field, usually we call it FormItem, which is mainly responsible for the interactive style of the field, such as the field title, the style of error prompts, etc., If we want to include more linkage, such as the linkage between certain data and FormItem, then we have to add the outer package container. There are many other attributes, which are not listed here. -从上面的思路中我们可以看到,为了解决联动问题,不管我们怎么抽象,最终还是会抽象出字段模型,它包含了字段相关的所有状态,只要去操作这些状态就能引发联动。 +From the above ideas, we can see that in order to solve the linkage problem, no matter how abstract we are, the field model will eventually be abstracted. It contains all the states related to the field. As long as these states are manipulated, linkage can be triggered. -关于精确渲染,我们已经确定可以选用类似 Mobx 的 Reactive 方案,虽然是重新造了一个轮子,但是,Reactive 这种模式始终还是很适合抽象响应式模型,所以基于 Reactive 的能力,Formily 经过不断试错与纠正,总算设计出了真正优雅的表单模型。这样的表单模型,解决的是表单领域问题,所以也称之为领域模型,有了这样的领域模型,我们就能让表单的联动变得可枚举可预测,这样也为后面要说的协议描述联动打下了坚实基础。 +Regarding accurate rendering, we have determined that we can choose a Reactive solution similar to Mobx. Although it is a reinvention of a wheel, the Reactive model is still very suitable for abstract responsive models. So based on the ability of Reactive, Formily, after constant trial and error and correction, finally designed a truly elegant form model. Such a form model solves the problem of the form domain, so it is also called a domain model. With such a domain model, we can make the linkage of the form enumerable and predictable, which also lays a solid foundation for the linkage of the protocol description to be discussed later. -### 路径系统 +### Path System -前面提到了表单领域模型中的字段模型,如果设计的更完备的话,其实不止是字段模型,必须还要有一个表单模型作为顶层模型,顶层模型管理着所有字段模型,每个字段都有着自己的路径,那如何查找这些字段呢?前面说到的联动关系,更多的是被动依赖关系,但是有些场景,我们就是要基于某个异步事件动作,去修改某个字段的状态,这里就涉及到如何优雅的查找某个字段,同样也是经过了大量的试错与纠正,Formily 独创的路径系统 @formily/path 很好的解决了这个问题,不仅仅是让字段查找变得优雅,它还能通过解构表达式去处理前后端数据结构不一致的恶心问题。 +The field model in the form domain model was mentioned earlier. If the design is more complete, it is not only a field model, but also a form model as the top-level model. The top-level model manages all the field models, and each field has its own Path. How to find these fields? The linkage relationship mentioned earlier is more of a passive dependency, but in some scenarios, we just need to modify the state of a field based on an asynchronous event action. Here is how to find a field elegantly. The same It has also undergone a lot of trial and error and correction. Formily's original path system @formily/path solves this problem very well. It not only makes the field lookup elegant, but it can also deal with the disgusting problem of inconsistent front-end and back-end data structures through destructuring expressions. -### 生命周期 +### Life Cycle -借助 Mobx 和路径系统,我们已经打造了一个较为完备的表单方案了,但是这样抽象了之后,我们的方案就像个黑盒,外界无法感知到方案内部状态流转过程,想要在某个过程阶段内实现一些逻辑则无法实现,所以,这里我们就需要另外一个概念了,生命周期,只要我们将整个表单生命周期作为事件钩子暴露给外界,这样就能做到了既有抽象,但又灵活的表单方案。 +With the help of Mobx and the path system, we have created a relatively complete form scheme, but after this abstraction, our scheme is like a black box, and the outside world cannot perceive the internal state flow process of the scheme. If you want to implement some logic in a certain process stage, you cannot achieve it. So, here we need another concept, the life cycle. As long as we expose the entire form life cycle as an event hook to the outside world, we can achieve an abstract but flexible form solution. -### 协议驱动 +### Protocol Driven -如果想要实现动态可配置表单,那必然是需要将表单结构变得可序列化,序列化的方式有很多种,可以是以 UI 为思路的 UI 描述协议,也可以是以数据为思路的数据描述协议,因为表单本身就是为了维护一份数据,那自然而然,对于表单场景而言,数据协议最适合不过,想要描述数据结构,现在业界最流行的就是 [JSON-Schema](https://json-schema.org/) 了,因为 JSON Schema 协议上本身就有很多校验相关的属性,这就天然和表单校验关联上了。那 UI 描述协议就真的不适合描述表单吗?No,UI 描述协议适合更通用的 UI 表达,描述表单当然不在话下,只是它会更偏前端协议,相反,JSON-Schema,在后端模型层,都是可表达的,在描述数据上更通用,所以两种协议,各有所长,只是在单纯表单领域,JSON-Schema 会更偏领域化一些。 +If you want to implement a dynamically configurable form, you must make the form structure serializable. +There are many ways to serialize, which can be a UI description protocol based on the UI, or a data description protocol based on the data. Because the form itself is to maintain a copy of data, it is natural that for the form scenario, the data protocol is the most suitable. To describe the data structure, [JSON-Schema](https://json-schema.org/) is now the most popular in the industry. Because the JSON Schema protocol itself has many verification-related attributes, this is naturally associated with form verification. Is the UI description protocol really not suitable for describing forms? No, the UI description protocol is suitable for more general UI expressions. Of course, the description form is not a problem, but it will be more front-end protocol. On the contrary, JSON-Schema is expressible at the back-end model layer, and is more versatile in describing data. Therefore, the two protocols have their own strengths, but in the field of pure forms, JSON-Schema will be more domain-oriented. -那么,如果选用 JSON-Schema,我们怎么描述 UI,怎么描述逻辑呢?单纯的描述数据,想要输出实际业务可用的表单页面,不太现实。 +So, if we choose JSON-Schema, how do we describe the UI and how do we describe the logic? It is not realistic to simply describe the data and output the form pages available for actual business. -[react-jsonschema-form](https://github.com/rjsf-team/react-jsonschema-form)的解法是,数据是数据,UI 是 UI,这样的好处是,各个协议都是非常纯净的协议,但是却带来了较大的维护成本和理解成本,用户要开发一个表单,需要不断的在两种协议心智上做切换,所以,如果从技术视角来看这样的拆分,其实是非常合理的,但是从产品视角来看的话,拆分则是把成本抛给了用户,所以,Formily 的表单协议会更加倾向于在 JSON-Schema 上做扩展。 +The solution of [react-jsonschema-form](https://github.com/rjsf-team/react-jsonschema-form) is that data is data and UI is UI. The advantage of this is that each protocol is a very pure protocol, but it brings a large maintenance cost and understanding cost. +To develop a form, users need to constantly switch between the two protocols mentally. Therefore, if you look at such a split from a technical perspective, it is very reasonable, but from a product perspective, the split is to throw the cost to the user. Therefore, Formily's form protocol will be more inclined to expand on JSON-Schema. -那么,如何扩展呢?为了不污染标准 JSON-Schema 属性,我们统一以`x-*`格式来表达扩展属性: +So, how to expand? In order not to pollute the standard JSON-Schema attributes, we uniformly express the extended attributes in the x-* format: ```json { "type": "string", - "title": "字符串", - "description": "这是一个字符串", + "title": "String", + "description": "This is a string", "x-component": "Input", "x-component-props": { - "placeholder": "请输入" + "placeholder": "please enter" } } ``` -这样看来,UI 协议与数据协议混合在一起,只要有一个统一的扩展约定,也还是能保证两种协议职责单一。 +In this way, the UI protocol and the data protocol are mixed together. As long as there is a unified extension agreement, the responsibilities of the two protocols can still be guaranteed to be single. -然后,如果想要在某些字段上包裹一个 UI 容器怎么办呢?这里,Formily 定义了一个新的 schema type,叫`void`。void 不陌生,W3C 规范里也有 void element,js 里也有 void 关键字,前者代表虚元素,后者代表虚指针,所以,在 JSON Schema 中,引入 void,代表一个虚数据节点,表示该节点并不占用实际数据结构。所以,我们可以这样: +Then, what if you want to wrap a UI container on certain fields? Here, Formily defines a new schema type called `void`. No stranger to void, there is also void element in W3C specification, and void keyword in js. The former represents virtual elements, and the latter represents virtual pointers. Therefore, in JSON Schema, void is introduced to represent a virtual data node, which means that the node does not occupy the actual data structure. So, we can do this: ```json { "type": "void", - "title": "卡片", - "description": "这是一个卡片", + "title": "card", + "description": "This is a card", "x-component": "Card", "properties": { "string": { "type": "string", - "title": "字符串", - "description": "这是一个字符串", + "title": "String", + "description": "This is a string", "x-component": "Input", "x-component-props": { - "placeholder": "请输入" + "placeholder": "please enter" } } } } ``` -这样就可以描述了一个 UI 容器了,因为可以描述 UI 容器,我们就能轻易封装一个场景化的组件了,比如 FormStep,那么我们怎么描述字段间联动呢?比如一个字段要控制另一个字段的显示隐藏。我们可以这样: +In this way, a UI container can be described. Because the UI container can be described, we can easily encapsulate a scene-based component, such as FormStep. So how do we describe the linkage between fields? For example, one field needs to control the display and hide of another field. We can do this: ```json { @@ -178,7 +180,7 @@ interface Field { "title": "Source", "x-component": "Input", "x-component-props": { - "placeholder": "请输入" + "placeholder": "please enter" } }, "target": { @@ -186,7 +188,7 @@ interface Field { "title": "Target", "x-component": "Input", "x-component-props": { - "placeholder": "请输入" + "placeholder": "please enter" }, "x-reactions": [ { @@ -209,7 +211,7 @@ interface Field { } ``` -借助`x-reactions`描述了 target 字段,依赖了 source 字段的值,如果值为`'123'`的时候则显示 target 字段,否则隐藏,这种联动方式是一种被动联动,那如果我们希望实现主动联动呢?可以这样: +The target field is described with the help of `x-reactions`, which depends on the value of the source field. If the value is `'123'`, the target field is displayed, otherwise it is hidden. This linkage method is a passive linkage. What if we want to achieve active linkage ? It can be like this: ```json { @@ -220,7 +222,7 @@ interface Field { "title": "Source", "x-component": "Input", "x-component-props": { - "placeholder": "请输入" + "placeholder": "please enter" }, "x-reactions": [ { @@ -244,43 +246,43 @@ interface Field { "title": "Target", "x-component": "Input", "x-component-props": { - "placeholder": "请输入" + "placeholder": "please enter" } } } } ``` -只需要将`x-reactions`换个位置,放到 source 字段上,然后再指定一个 target 即可。 +Just change the location of `x-reactions`, put it on the source field, and then specify a target. -可以看到,我们的联动,其实核心是基于: +It can be seen that our linkage is actually based on: -- 条件 -- 条件满足的动作 -- 条件不满足的动作 +- condition +- Condition-satisfied action +- Unsatisfied action -来实现的,因为内部状态管理借助了 类似 Mobx 的[@formily/reactive](https://reactive.formilyjs.org)方案,所以,Formily 很轻松的就实现了被动和主动联动场景,覆盖了绝大多数业务需求。 +To achieve. Because the internal state management uses the [@formily/reactive](https://reactive.formilyjs.org) solution similar to Mobx, Formily easily realizes passive and active linkage scenarios, covering most business needs. -所以,我们的表单完全可以使用协议来描述了,不管是再复杂的布局,还是很复杂的联动,都能做到可配置。 +Therefore, our form can be described by protocol, and it can be configurable no matter how complicated the layout is or the linkage is very complicated. -### 分层架构 +### Layered Architecture -前面讲了对于一开始的各种问题的解法,那么现在我们如何设计才能让 Formily 更加自洽且优雅呢? +I talked about the solutions to various problems at the beginning, so how do we design now to make Formily more self-consistent and elegant? -![](https://img.alicdn.com/imgextra/i4/O1CN01XebYW51E96eP1AcwZ_!!6000000000308-55-tps-1939-1199.svg) +![](https://img.alicdn.com/imgextra/i3/O1CN0191vNVu1TYxFZA3KGN_!!6000000002395-55-tps-1939-1199.svg) -这张图主要将 Formily 分为了内核层,UI 桥接层,扩展组件层,和配置应用层。 +This picture mainly divides Formily into the kernel layer, UI bridge layer, extended component layer, and configuration application layer. -内核层是 UI 无关的,它保证了用户管理的逻辑和状态是不耦合任何一个框架,这样有几个好处: +The kernel layer is UI-independent. It ensures that the logic and state of user management are not coupled to any framework. This has several advantages: -- 逻辑与 UI 框架解耦,未来做框架级别的迁移,业务代码无需大范围重构 -- 学习成本统一,如果用户使用了@formily/react,以后业务迁移@formily/vue,用户不需要重新学习 +- Logic and UI framework are decoupled, and framework-level migration will be done in the future, without extensive refactoring of business code. +- The learning cost is uniform. If the user uses @formily/react, the business will be migrated to @formily/vue in the future, and the user does not need to learn again. -JSON Schema 独立存在,给 UI 桥接层消费,保证了协议驱动在不同 UI 框架下的绝对一致性,不需要重复实现协议解析逻辑。 +JSON Schema exists independently and is consumed by the UI bridging layer, ensuring the absolute consistency of protocol drivers under different UI frameworks, and there is no need to repeatedly implement protocol parsing logic. -扩展组件层,提供一系列表单场景化组件,保证用户开箱即用。无需花大量时间做二次开发。 +Extend the component layer to provide a series of form scene components to ensure that users can use it out of the box. No need to spend a lot of time for secondary development. -## 竞品对比 +## Competitive Product Comparison ```tsx /** @@ -307,114 +309,114 @@ const text = (content, tooltips) => { const dataSource = [ { - feature: '自定义组件接入成本', - antd: '4.x接入成本低', - fusion: '高', - formik: '低', - finalForm: '低', - schemaForm: text('高', '因为耦合bootstrap'), - hookForm: text('高', '因为耦合React Ref'), - 'formily1.x': '低', - 'formily2.x': '低', + feature: 'Custom component access cost', + antd: '4.x low access cost', + fusion: 'high', + formik: 'low', + finalForm: 'low', + schemaForm: text('high', 'Because of coupling bootstrap'), + hookForm: text('high', 'Because of coupling React Ref'), + 'formily1.x': 'low', + 'formily2.x': 'low', }, { - feature: '性能', - antd: text('4.x性能较好', '只解决了值同步精确渲染'), - fusion: '差', - formik: '差', - finalForm: text('较好', '但只解决了值同步精确渲染'), - schemaForm: '差', - hookForm: text('好', '但只解决了值同步精确渲染'), - 'formily1.x': text('非常好', '能解决联动过程中的精确渲染'), - 'formily2.x': text('非常好', '能解决联动过程中的精确渲染'), + feature: 'performance', + antd: text('4.x performance is better', 'Only solved the value synchronization and accurate rendering'), + fusion: 'bad', + formik: 'bad', + finalForm: text('better', 'But only solved the value synchronization and accurate rendering'), + schemaForm: 'bad', + hookForm: text('good', 'But only solved the value synchronization and accurate rendering'), + 'formily1.x': text('excellent', 'Can solve the precise rendering in the linkage process'), + 'formily2.x': text('excellent', 'Can solve the precise rendering in the linkage process'), }, { - feature: '是否支持动态渲染', - antd: '否', - fusion: '否', - formik: '否', - finalForm: '否', - schemaForm: '是', - hookForm: '否', - 'formily1.x': '是', - 'formily2.x': '是', + feature: 'Whether to support dynamic rendering', + antd: 'no', + fusion: 'no', + formik: 'no', + finalForm: 'no', + schemaForm: 'yes', + hookForm: 'no', + 'formily1.x': 'yes', + 'formily2.x': 'yes', }, { - feature: '是否开箱即用', - antd: '是', - fusion: '是', - formik: '否', - finalForm: '否', - schemaForm: '是', - hookForm: '否', - 'formily1.x': '是', - 'formily2.x': '是', + feature: 'Whether to use out of the box', + antd: 'yes', + fusion: 'yes', + formik: 'no', + finalForm: 'no', + schemaForm: 'yes', + hookForm: 'no', + 'formily1.x': 'yes', + 'formily2.x': 'yes', }, { - feature: '是否支持跨端', - antd: '否', - fusion: '否', - formik: '否', - finalForm: '否', - schemaForm: '否', - hookForm: '否', - 'formily1.x': '是', - 'formily2.x': '是', + feature: 'Whether to support cross-terminal', + antd: 'no', + fusion: 'no', + formik: 'no', + finalForm: 'no', + schemaForm: 'no', + hookForm: 'no', + 'formily1.x': 'yes', + 'formily2.x': 'yes', }, { - feature: '开发效率', - antd: '一般', - fusion: '一般', - formik: '一般', - finalForm: '一般', - schemaForm: text('低', '源码开发需要手工维护JSON'), - hookForm: '一般', - 'formily1.x': '高', - 'formily2.x': '高', + feature: 'Development efficiency', + antd: 'general', + fusion: 'generalv', + formik: 'general', + finalForm: 'general', + schemaForm: text('low', 'Source code development requires manual maintenance of JSON'), + hookForm: 'general', + 'formily1.x': 'high', + 'formily2.x': 'high', }, { - feature: '学习成本', - antd: '低', - fusion: '低', - formik: '低', - finalForm: '高', - schemaForm: '高', - hookForm: '低', - 'formily1.x': '很高', - 'formily2.x': text('高', '概念大量减少'), + feature: 'Learning cost', + antd: 'easy', + fusion: 'easy', + formik: 'easy', + finalForm: 'hard', + schemaForm: 'hard', + hookForm: 'easy', + 'formily1.x': 'very hard', + 'formily2.x': text('hard', 'The concept is drastically reduced'), }, { - feature: '视图代码可维护性', - antd: text('低', '大量条件表达式'), - fusion: text('低', '大量条件表达式'), - formik: text('低', '大量条件表达式'), - finalForm: text('低', '大量条件表达式'), - schemaForm: '高', - hookForm: text('低', '大量条件表达式'), - 'formily1.x': '高', - 'formily2.x': '高', + feature: 'View code maintainability', + antd: text('bad', 'Lots of conditional expressions'), + fusion: text('bad', 'Lots of conditional expressions'), + formik: text('bad', 'Lots of conditional expressions'), + finalForm: text('bad', 'Lots of conditional expressions'), + schemaForm: 'good', + hookForm: text('bad', 'Lots of conditional expressions'), + 'formily1.x': 'good', + 'formily2.x': 'good', }, { - feature: '场景化封装能力', - antd: '无', - fusion: '无', - formik: '无', - finalForm: '无', - schemaForm: '有', - hookForm: '无', - 'formily1.x': '有', - 'formily2.x': '有', + feature: 'Scenario-based packaging capabilities', + antd: 'no', + fusion: 'no', + formik: 'no', + finalForm: 'no', + schemaForm: 'yes', + hookForm: 'no', + 'formily1.x': 'yes', + 'formily2.x': 'yes', }, { - feature: '是否支持表单预览态', - antd: '否', - fusion: '是', - formik: '否', - finalForm: '否', - schemaForm: '否', - hookForm: '否', - 'formily1.x': '是', - 'formily2.x': '是', + feature: 'Whether to support form preview', + antd: 'no', + fusion: 'yes', + formik: 'no', + finalForm: 'no', + schemaForm: 'no', + hookForm: 'no', + 'formily1.x': 'yes', + 'formily2.x': 'yes', }, ] @@ -427,7 +429,7 @@ export default () => { scroll={{ x: 1600 }} size="small" > - + @@ -449,49 +451,34 @@ export default () => { } ``` -## 核心优势 - -- 高性能 -- 开箱即用 -- 联动逻辑实现高效 -- 跨端能力,逻辑可跨框架,跨终端复用 -- 动态渲染能力 - -## 核心劣势 - -- 学习成本较高,虽然 2.x 已经在大量收敛概念,但还是存在一定的学习成本。 - -## 谁在使用? - -- 阿里巴巴 - - 数字供应链事业部 - - 淘系技术部 - - 飞猪 - - 阿里云 - - 蚂蚁 - - 政务平台 - - 大文娱 - - 盒马 - - 阿里妈妈 - - 数据平台 - - 飞猪 - - ICBU - - 口碑 - - 钉钉 - - 天猫超市、天猫国际、阿里健康、农村淘宝、淘宝心选 -- 腾讯 -- 字节跳动 +## Core Advantages + +- high performance +- Out of the box +- Linkage logic to achieve high efficiencyv +- Cross-terminal capability, logic can be cross-frame, cross-terminal reuse +- Dynamic rendering capability + +## Core Disadvantage + +- The learning cost is relatively high. Although 2.x has already converged a large number of concepts, there is still a certain learning cost. + +## Who is using it? + +- Alibaba +- Tencent +- ByteDance ## Q/A -问:有了 Vue 了,为什么还需要提供@formily/vue? +Q: Now that I have Vue, why do I still need to provide @formily/vue? -答:Vue 是一个 UI 框架,它解决的问题是更大范围的 UI 问题,虽然它的 reactive 能力在表单场景上表现出众,至少比原生 React 写表单要方便,但是如果在更复杂的表单场景上,我们还是需要做很多抽象和封装,所以@formily/vue 就是为了帮您做这些抽象封装的事情,真正让您高效便捷的开发出超复杂表单应用。 +Answer: Vue is a UI framework. The problem it solves is a wider range of UI problems. Although its reactive ability is outstanding in form scenarios, at least it is more convenient than native React to write forms, but if it is in more complex form scenarios , We still need to do a lot of abstraction and encapsulation, so @formily/vue is to help you do these abstract encapsulation things, really let you develop super-complex form applications efficiently and conveniently. -问:Formily2.x 相比于 1.x 最大的优势是什么? +Q: What is the biggest advantage of Formily2.x compared to 1.x? -答:学习成本的大大降低,对,核心是为了让用户更快速的理解 Formily,我们在 2.x 设计的过程中极力的避免出现各种隐晦逻辑,边界问题,同时因为移除了rxjs/styled-components的依赖,整体体积大大降低 +Answer: The cost of learning, yes, the core is to allow users to understand Formily more quickly. We have tried our best to avoid all kinds of obscure logic and boundary problems during the 2.x design process. -问:Formily2.x 的浏览器兼容性如何? +Q: What is the browser compatibility of Formily 2.x? -答:不支持 IE,因为 Reactive 的实现强依赖 Proxy +Answer: IE is not supported, because the implementation of Reactive strongly relies on Proxy. diff --git a/docs/guide/index.zh-CN.md b/docs/guide/index.zh-CN.md new file mode 100644 index 00000000000..3dc89923c9b --- /dev/null +++ b/docs/guide/index.zh-CN.md @@ -0,0 +1,497 @@ +# 介绍 + +## 问题 + +众所周知,表单场景一直都是前端中后台领域最复杂的场景,它的复杂度主要在哪里呢? + +- 字段数量多,如何让性能不随字段数量增加而变差? +- 字段关联逻辑复杂,如何更简单的实现复杂的联动逻辑?字段与字段关联时,如何保证不影响表单性能? + + - 一对多(异步) + - 多对一(异步) + - 多对多(异步) + +- 表单数据管理复杂 + - 表单值转换逻辑复杂(前后端格式不一致) + - 同步默认值与异步默认值合并逻辑复杂 + - 跨表单数据通信,如何让性能不随字段数量增加而变差? +- 表单状态管理复杂 + - 着重提自增列表场景,如何让数组数据在移动,删除过程中,字段状态能够做到跟随移动? +- 表单的场景化复用 + - 查询列表 + - 弹窗/抽屉表单 + - 分步表单 + - 选项卡表单 +- 动态渲染述求很强烈 + - 字段配置化,让非专业前端也能快速搭建复杂表单 + - 跨端渲染,一份 JSON Schema,多端适配 + - 如何在表单协议中描述布局? + - 纵向布局 + - 横向布局 + - 网格布局 + - 弹性布局 + - 自由布局 + - 如何在表单协议中描述逻辑? + +这么多问题,怎么解决,想想就头大,但是我们还是得想办法解决,不仅要解决,还要优雅的解决,阿里数字供应链团队,在经历了大量的中后台实践和探索之后,总算沉淀出了 **Formily 表单解决方案** ,以上提到的所有问题,在经历了 UForm 到 Formily1.x,直到 Formily2.x 总算做到了 **优雅解决** 的程度。那 Formily2.x 是如何解决这些问题的呢? + +## 解法 + +为了解决以上问题,我们可以对问题做进一步提炼,得出可突破的方向。 + +### 精确渲染 + +在 React 场景下实现一个表单需求,因为要收集表单数据,实现一些联动需求,大多数都是通过 setState 来实现字段数据收集,这样实现非常简单,心智成本非常低,但是却又引入了性能问题,因为每次输入都会导致所有字段全量渲染,虽然在 DOM 更新层面是有 diff,但是 diff 也是有计算成本的,浪费了很多计算资源,如果用时间复杂度来看的话,初次渲染表单是 O(n),字段输入时也是 O(n),这样明显是不合理的。 + +历史的经验总是对人类有帮助的,几十年前,人类创造出了 MVVM 设计模式。这样的设计模式核心是将视图模型抽象出来,然后在 DSL 模板层消费,DSL 借助某种依赖收集机制,然后在视图模型中统一调度,保证每次输入都是精确渲染的,这就是工业级的 GUI 形态! + +刚好,github 社区为这样的 MVVM 模型抽象出了一个叫 [Mobx](https://github.com/mobxjs/mobx) 的状态管理解决方案,Mobx 最核心的能力就是它的依赖追踪机制和响应式模型的抽象能力。 + +所以,借助 Mobx,完全可以解决表单字段输入过程中的 O(n)问题,而且是可以很优雅的解决,但是 Formily2.x 在实现的过程中发现 Mobx 还是存在一些不兼容 Formily 核心思想的问题,最终,只能重新造了一个轮子,延续 Mobx 的核心思想的 [@formily/reactive](https://reactive.formilyjs.org) + +这里提一下 [react-hook-form](https://github.com/react-hook-form/react-hook-form) ,非常流行,号称业界性能第一的表单方案,我们看看它最简单的案例: + +```tsx pure +import React from 'react' +import ReactDOM from 'react-dom' +import { useForm } from 'react-hook-form' + +function App() { + const { register, handleSubmit, errors } = useForm() // initialize the hook + const onSubmit = (data) => { + console.log(data) + } + + return ( +
+ {/* register an input */} + + {errors.lastname && 'Last name is required.'} + + {errors.age && 'Please enter number for age.'} + +
+ ) +} + +ReactDOM.render(, document.getElementById('root')) +``` + +虽然值管理做到了精确渲染,但是在触发校验的时候,还是会导致表单全量渲染,因为 errors 状态的更新,是必须要整体受控渲染才能实现同步,这仅仅只是校验会全量渲染,其实还有联动,react-hook-form 要实现联动,同样是需要整体受控渲染才能实现联动。所以,如果要真正实现精确渲染,非 Reactive 不可! + +### 领域模型 + +前面问题中有提到表单的联动是非常复杂的,包含了字段间的各种关系,我们想象一下,大多数表单联动,基本上都是基于某些字段的值引发的联动,但是,实际业务需求可能会比较恶心,不仅要基于某些字段值引发联动,还会基于其他副作用值引发联动,比如应用状态,服务端数据状态,页面 URL,某个字段 UI 组件内部数据,当前字段自身的其他数据状态,某些特殊异步事件等等。用张图来描述: + +![image-20210202081316031](//img.alicdn.com/imgextra/i3/O1CN01LWjBSt251w5BtGHW2_!!6000000007467-55-tps-1100-432.svg) + +从上图可以看到,想要达成一个联动关系,核心是将字段的某些状态属性与某些数据关联起来,这里的某些数据可以是外界数据,也可以是自身数据,比如字段的显示/隐藏与某些数据的关联,又比如字段的值与某些数据关联,还比如字段的禁用/编辑与某些数据关联,就举了 3 个例子,我们其实已经抽象出了一个最简单的 Field 模型: + +```typescript +interface Field { + value: any + visible: boolean + disabled: boolean +} +``` + +当然,Field 模型仅仅只有这 3 个属性吗?肯定不是,如果我们要表达一个字段,那么字段的路径一定要有,因为要描述整个表单树结构,同时,我们还要管理起字段对应 UI 组件的属性,比如 Input 和 Select 都有它的属性,举个例子,Input 的 placeholder 与某些数据关联,或者 Select 的下拉选项与某些数据关联,这样就能理解了吧。所以,我们的 Field 模型可以是这样: + +``` +interface Field { + path:string[], + value:any, + visible:boolean, + disabled:boolean, + component:[Component,ComponentProps] +} +``` + +我们加了 component 属性,它代表了字段所对应的 UI 组件和 UI 组件属性,这样就实现了某些数据与字段组件属性关联,甚至是与字段组件关联的能力。还有吗?当然还有,比如字段的外包裹容器,通常我们都叫 FormItem,它主要负责字段的外围的交互样式,比如字段标题,错误提示的样式等等,如果我们想要囊括更多联动,比如某些数据与 FormItem 的联动,那就得把外包裹容器也加进去。还有很多很多属性,这里没法一一列举。 + +从上面的思路中我们可以看到,为了解决联动问题,不管我们怎么抽象,最终还是会抽象出字段模型,它包含了字段相关的所有状态,只要去操作这些状态就能引发联动。 + +关于精确渲染,我们已经确定可以选用类似 Mobx 的 Reactive 方案,虽然是重新造了一个轮子,但是,Reactive 这种模式始终还是很适合抽象响应式模型,所以基于 Reactive 的能力,Formily 经过不断试错与纠正,总算设计出了真正优雅的表单模型。这样的表单模型,解决的是表单领域问题,所以也称之为领域模型,有了这样的领域模型,我们就能让表单的联动变得可枚举可预测,这样也为后面要说的协议描述联动打下了坚实基础。 + +### 路径系统 + +前面提到了表单领域模型中的字段模型,如果设计的更完备的话,其实不止是字段模型,必须还要有一个表单模型作为顶层模型,顶层模型管理着所有字段模型,每个字段都有着自己的路径,那如何查找这些字段呢?前面说到的联动关系,更多的是被动依赖关系,但是有些场景,我们就是要基于某个异步事件动作,去修改某个字段的状态,这里就涉及到如何优雅的查找某个字段,同样也是经过了大量的试错与纠正,Formily 独创的路径系统 @formily/path 很好的解决了这个问题,不仅仅是让字段查找变得优雅,它还能通过解构表达式去处理前后端数据结构不一致的恶心问题。 + +### 生命周期 + +借助 Mobx 和路径系统,我们已经打造了一个较为完备的表单方案了,但是这样抽象了之后,我们的方案就像个黑盒,外界无法感知到方案内部状态流转过程,想要在某个过程阶段内实现一些逻辑则无法实现,所以,这里我们就需要另外一个概念了,生命周期,只要我们将整个表单生命周期作为事件钩子暴露给外界,这样就能做到了既有抽象,但又灵活的表单方案。 + +### 协议驱动 + +如果想要实现动态可配置表单,那必然是需要将表单结构变得可序列化,序列化的方式有很多种,可以是以 UI 为思路的 UI 描述协议,也可以是以数据为思路的数据描述协议,因为表单本身就是为了维护一份数据,那自然而然,对于表单场景而言,数据协议最适合不过,想要描述数据结构,现在业界最流行的就是 [JSON-Schema](https://json-schema.org/) 了,因为 JSON Schema 协议上本身就有很多校验相关的属性,这就天然和表单校验关联上了。那 UI 描述协议就真的不适合描述表单吗?No,UI 描述协议适合更通用的 UI 表达,描述表单当然不在话下,只是它会更偏前端协议,相反,JSON-Schema,在后端模型层,都是可表达的,在描述数据上更通用,所以两种协议,各有所长,只是在单纯表单领域,JSON-Schema 会更偏领域化一些。 + +那么,如果选用 JSON-Schema,我们怎么描述 UI,怎么描述逻辑呢?单纯的描述数据,想要输出实际业务可用的表单页面,不太现实。 + +[react-jsonschema-form](https://github.com/rjsf-team/react-jsonschema-form)的解法是,数据是数据,UI 是 UI,这样的好处是,各个协议都是非常纯净的协议,但是却带来了较大的维护成本和理解成本,用户要开发一个表单,需要不断的在两种协议心智上做切换,所以,如果从技术视角来看这样的拆分,其实是非常合理的,但是从产品视角来看的话,拆分则是把成本抛给了用户,所以,Formily 的表单协议会更加倾向于在 JSON-Schema 上做扩展。 + +那么,如何扩展呢?为了不污染标准 JSON-Schema 属性,我们统一以`x-*`格式来表达扩展属性: + +```json +{ + "type": "string", + "title": "字符串", + "description": "这是一个字符串", + "x-component": "Input", + "x-component-props": { + "placeholder": "请输入" + } +} +``` + +这样看来,UI 协议与数据协议混合在一起,只要有一个统一的扩展约定,也还是能保证两种协议职责单一。 + +然后,如果想要在某些字段上包裹一个 UI 容器怎么办呢?这里,Formily 定义了一个新的 schema type,叫`void`。void 不陌生,W3C 规范里也有 void element,js 里也有 void 关键字,前者代表虚元素,后者代表虚指针,所以,在 JSON Schema 中,引入 void,代表一个虚数据节点,表示该节点并不占用实际数据结构。所以,我们可以这样: + +```json +{ + "type": "void", + "title": "卡片", + "description": "这是一个卡片", + "x-component": "Card", + "properties": { + "string": { + "type": "string", + "title": "字符串", + "description": "这是一个字符串", + "x-component": "Input", + "x-component-props": { + "placeholder": "请输入" + } + } + } +} +``` + +这样就可以描述了一个 UI 容器了,因为可以描述 UI 容器,我们就能轻易封装一个场景化的组件了,比如 FormStep,那么我们怎么描述字段间联动呢?比如一个字段要控制另一个字段的显示隐藏。我们可以这样: + +```json +{ + "type": "object", + "properties": { + "source": { + "type": "string", + "title": "Source", + "x-component": "Input", + "x-component-props": { + "placeholder": "请输入" + } + }, + "target": { + "type": "string", + "title": "Target", + "x-component": "Input", + "x-component-props": { + "placeholder": "请输入" + }, + "x-reactions": [ + { + "dependencies": ["source"], + "when": "{{$deps[0] == '123'}}", + "fulfill": { + "state": { + "visible": true + } + }, + "otherwise": { + "state": { + "visible": false + } + } + } + ] + } + } +} +``` + +借助`x-reactions`描述了 target 字段,依赖了 source 字段的值,如果值为`'123'`的时候则显示 target 字段,否则隐藏,这种联动方式是一种被动联动,那如果我们希望实现主动联动呢?可以这样: + +```json +{ + "type": "object", + "properties": { + "source": { + "type": "string", + "title": "Source", + "x-component": "Input", + "x-component-props": { + "placeholder": "请输入" + }, + "x-reactions": [ + { + "when": "{{$self.value == '123'}}", + "target": "target", + "fulfill": { + "state": { + "visible": true + } + }, + "otherwise": { + "state": { + "visible": false + } + } + } + ] + }, + "target": { + "type": "string", + "title": "Target", + "x-component": "Input", + "x-component-props": { + "placeholder": "请输入" + } + } + } +} +``` + +只需要将`x-reactions`换个位置,放到 source 字段上,然后再指定一个 target 即可。 + +可以看到,我们的联动,其实核心是基于: + +- 条件 +- 条件满足的动作 +- 条件不满足的动作 + +来实现的,因为内部状态管理借助了 类似 Mobx 的[@formily/reactive](https://reactive.formilyjs.org)方案,所以,Formily 很轻松的就实现了被动和主动联动场景,覆盖了绝大多数业务需求。 + +所以,我们的表单完全可以使用协议来描述了,不管是再复杂的布局,还是很复杂的联动,都能做到可配置。 + +### 分层架构 + +前面讲了对于一开始的各种问题的解法,那么现在我们如何设计才能让 Formily 更加自洽且优雅呢? + +![](https://img.alicdn.com/imgextra/i4/O1CN01XebYW51E96eP1AcwZ_!!6000000000308-55-tps-1939-1199.svg) + +这张图主要将 Formily 分为了内核层,UI 桥接层,扩展组件层,和配置应用层。 + +内核层是 UI 无关的,它保证了用户管理的逻辑和状态是不耦合任何一个框架,这样有几个好处: + +- 逻辑与 UI 框架解耦,未来做框架级别的迁移,业务代码无需大范围重构 +- 学习成本统一,如果用户使用了@formily/react,以后业务迁移@formily/vue,用户不需要重新学习 + +JSON Schema 独立存在,给 UI 桥接层消费,保证了协议驱动在不同 UI 框架下的绝对一致性,不需要重复实现协议解析逻辑。 + +扩展组件层,提供一系列表单场景化组件,保证用户开箱即用。无需花大量时间做二次开发。 + +## 竞品对比 + +```tsx +/** + * inline: true + */ +import React from 'react' +import { Table, Tooltip } from 'antd' +import { QuestionCircleOutlined } from '@ant-design/icons' +import 'antd/lib/table/style' + +const text = (content, tooltips) => { + if (tooltips) { + return ( +
+ {content} + + + +
+ ) + } + return content +} + +const dataSource = [ + { + feature: '自定义组件接入成本', + antd: '4.x接入成本低', + fusion: '高', + formik: '低', + finalForm: '低', + schemaForm: text('高', '因为耦合bootstrap'), + hookForm: text('高', '因为耦合React Ref'), + 'formily1.x': '低', + 'formily2.x': '低', + }, + { + feature: '性能', + antd: text('4.x性能较好', '只解决了值同步精确渲染'), + fusion: '差', + formik: '差', + finalForm: text('较好', '但只解决了值同步精确渲染'), + schemaForm: '差', + hookForm: text('好', '但只解决了值同步精确渲染'), + 'formily1.x': text('非常好', '能解决联动过程中的精确渲染'), + 'formily2.x': text('非常好', '能解决联动过程中的精确渲染'), + }, + { + feature: '是否支持动态渲染', + antd: '否', + fusion: '否', + formik: '否', + finalForm: '否', + schemaForm: '是', + hookForm: '否', + 'formily1.x': '是', + 'formily2.x': '是', + }, + { + feature: '是否开箱即用', + antd: '是', + fusion: '是', + formik: '否', + finalForm: '否', + schemaForm: '是', + hookForm: '否', + 'formily1.x': '是', + 'formily2.x': '是', + }, + { + feature: '是否支持跨端', + antd: '否', + fusion: '否', + formik: '否', + finalForm: '否', + schemaForm: '否', + hookForm: '否', + 'formily1.x': '是', + 'formily2.x': '是', + }, + { + feature: '开发效率', + antd: '一般', + fusion: '一般', + formik: '一般', + finalForm: '一般', + schemaForm: text('低', '源码开发需要手工维护JSON'), + hookForm: '一般', + 'formily1.x': '高', + 'formily2.x': '高', + }, + { + feature: '学习成本', + antd: '低', + fusion: '低', + formik: '低', + finalForm: '高', + schemaForm: '高', + hookForm: '低', + 'formily1.x': '很高', + 'formily2.x': text('高', '概念大量减少'), + }, + { + feature: '视图代码可维护性', + antd: text('低', '大量条件表达式'), + fusion: text('低', '大量条件表达式'), + formik: text('低', '大量条件表达式'), + finalForm: text('低', '大量条件表达式'), + schemaForm: '高', + hookForm: text('低', '大量条件表达式'), + 'formily1.x': '高', + 'formily2.x': '高', + }, + { + feature: '场景化封装能力', + antd: '无', + fusion: '无', + formik: '无', + finalForm: '无', + schemaForm: '有', + hookForm: '无', + 'formily1.x': '有', + 'formily2.x': '有', + }, + { + feature: '是否支持表单预览态', + antd: '否', + fusion: '是', + formik: '否', + finalForm: '否', + schemaForm: '否', + hookForm: '否', + 'formily1.x': '是', + 'formily2.x': '是', + }, +] + +export default () => { + return ( + + + + + + + + + + +
+ ) +} +``` + +## 核心优势 + +- 高性能 +- 开箱即用 +- 联动逻辑实现高效 +- 跨端能力,逻辑可跨框架,跨终端复用 +- 动态渲染能力 + +## 核心劣势 + +- 学习成本较高,虽然 2.x 已经在大量收敛概念,但还是存在一定的学习成本。 + +## 谁在使用? + +- 阿里巴巴 + - 数字供应链事业部 + - 淘系技术部 + - 飞猪 + - 阿里云 + - 蚂蚁 + - 政务平台 + - 大文娱 + - 盒马 + - 阿里妈妈 + - 数据平台 + - 飞猪 + - ICBU + - 口碑 + - 钉钉 + - 天猫超市、天猫国际、阿里健康、农村淘宝、淘宝心选 +- 腾讯 +- 字节跳动 + +## Q/A + +问:有了 Vue 了,为什么还需要提供@formily/vue? + +答:Vue 是一个 UI 框架,它解决的问题是更大范围的 UI 问题,虽然它的 reactive 能力在表单场景上表现出众,至少比原生 React 写表单要方便,但是如果在更复杂的表单场景上,我们还是需要做很多抽象和封装,所以@formily/vue 就是为了帮您做这些抽象封装的事情,真正让您高效便捷的开发出超复杂表单应用。 + +问:Formily2.x 相比于 1.x 最大的优势是什么? + +答:学习成本的大大降低,对,核心是为了让用户更快速的理解 Formily,我们在 2.x 设计的过程中极力的避免出现各种隐晦逻辑,边界问题,同时因为移除了rxjs/styled-components的依赖,整体体积大大降低 + +问:Formily2.x 的浏览器兼容性如何? + +答:不支持 IE,因为 Reactive 的实现强依赖 Proxy diff --git a/docs/guide/learn-formily.md b/docs/guide/learn-formily.md index 4d56599487f..e6e9ebef0a9 100644 --- a/docs/guide/learn-formily.md +++ b/docs/guide/learn-formily.md @@ -1,46 +1,48 @@ -# 如何学习 Formily +# How to learn Formily -## 学习建议 +## Study Suggestion -Formily 用一句话来描述,它就是一个抽象了表单领域模型的 MVVM 表单解决方案,所以,如果你想深入使用 Formily,那必须学习并了解 Formily 的领域模型到底是咋样的,它到底解决了哪些问题,了解完领域模型之后,其实就是如何消费这个领域模型的视图层了,这一层就只需要看具体组件的文档即可了。 +To describe Formily in one sentence, it is an MVVM form solution that abstracts the form domain model. Therefore, if you want to use Formily in depth, you must learn and understand what Formily's domain model is like and what problems does it solve. After understanding the domain model, it is actually how to consume the view layer of this domain model. This layer only needs to look at the documentation of the specific components. -## 关于文档 +## About the documentation -因为 Formily 的学习成本还是比较高的,想要快速了解 Formily 的全貌,最重要的还是看文档,只是文档怎么看,从哪里看会比较重要,下面我们针对不同用户给出了不同的文档学习路线。 +Because Formily’s learning costs are still relatively high, if you want to quickly understand the full picture of Formily, the most important thing is to read the documentation. It's just how to look at the document and where it will be more important. Below we give different document learning routes for different users. -### 入门级用户 +### Entry-level user -- 引言介绍,因为你要了解 Formily 的核心思路,是否适合你的业务场景。 -- 快速开始,从最简单的例子学习实际 Formily 使用都是怎么使用的。 -- 组件文档/核心库文档,因为 Formily 为你已经封装好了大多数开箱即用的组件,遇到组件相关的问题,就像查字典一样的去查看组件文档即可。 -- 场景案例,从具体的场景出发,看看什么才是这个场景下的最佳实践。 +- Introduction, because you need to understand Formily's core ideas and whether it is suitable for your business scenario. +- Quick start, learn how to use Formily in practice from the simplest example. +- Component documentation/core library documentation, because Formily has already encapsulated most of the out-of-the-box components for you. If you encounter component-related problems, you can just check the component documentation just like looking up a dictionary. +- Scenario case, starting from the specific scenario, see what is the best practice in this scenario. -### 进阶级用户 +### Advanced users -- 仔细消化核心概念,更深入的理解 Formily -- 进阶指南,主要学习更高级的使用方式,比如自定义组件,从简单自定义组件到超复杂自定义组件 -- 随时查阅组件文档/核心库文档,加深记忆 -- 对于自定义组件开发上的细节问题,最佳实践,推荐直接看@formily/antd 或者@formily/next 的源码,因为这就是样板代码,跟实际业务场景息息相关。 +- Digest the core concepts carefully and have a deeper understanding of Formily. +- Advanced guide, mainly to learn more advanced usage methods, such as custom components, from simple custom components to super complex custom components. +- Read component documents/core library documents at any time to deepen memory +- For the details and best practices of custom component development, it is recommended to look directly at the source code of @formily/antd or @formily/next, because this is the boilerplate code and is closely related to the actual business scenario. -### 源码共建者 +### Source code co-builder -- 贡献指南,了解最基本的贡献姿势 -- 阅读文档,如果发现文档有缺陷,可以提 PR 修复 -- 阅读单元测试,了解每个测试用例所对应的实现细节,如果发现有遗漏测试用例,可以提 PR -- 阅读源码,如果发现源码有 Bug,可以提 PR +- Contribution guide, understand the most basic contribution posture. +- Read the document, if you find that the document is defective, you can submit a PR to fix it. +- Read the unit test to understand the implementation details corresponding to each test case. If you find that there are missing test cases, you can submit a PR. +- Read the source code, if you find a bug in the source code, you can raise a PR. -注意修改源码,必须要带上单元测试 +Pay attention to modify the source code, you must bring unit tests -## 关于提问 +## About the question -如果在开发的过程中遇到问题,推荐使用文档上方的搜索功能快速搜索文档内容,快速解决,如果搜索不到的,推荐到 [论坛](https://github.com/alibaba/formily/discussions) 中提问,这里方便记录,如果遇到非常紧急的问题,可以在钉钉群里 @白玄 帮忙解决。**非常不推荐文档都不看,就直接问各种基础问题,这样很低效** +If you encounter problems during the development process, it is recommended to use the search function at the top of the document to quickly search for the content of the document and solve it quickly. If you can’t find it, I recommend you to ask questions in the [forum](https://github.com/alibaba/formily/discussions). It is convenient to record. If you encounter a very urgent problem, you can help solve it in the Dingding group @白玄. **It is not recommended to ask various basic questions directly without reading the document, which is very inefficient** -## 关于 Bug +## About the bug -如果在开发过程中发现不符合预期的行为,并能够以最小案例复现的,可以给 Formily 提[Issue](https://github.com/alibaba/formily/issues) ,非常不推荐将问题记录在 issue 里,会打乱 Issue 的信息流,同时一定注意,**提 Issue 的时候要带上最小可复现的链接地址**,方便开发者快速定位问题,快速修复,而不是在一堆代码里找 Bug。 +If you find behaviors that do not meet expectations during the development process and can be reproduced in the smallest case, you can submit an [issue](https://github.com/alibaba/formily/issues) to Formily +It is strongly not recommended to record the problem in the issue, which will disrupt the information flow of Issue. At the same time, **be sure to bring the smallest reproducible link address when mentioning Issue**, so that developers can quickly locate the problem and fix it quickly, instead of Find bugs in a bunch of codes. -## 关于 Feature Request +## About Feature Request + +If during the development process you find that some of Formily's designs are not good, or can be improved better, you can submit your own ideas in the [forum](https://github.com/alibaba/formily/discussions) -如果在开发过程中发现 Formily 的某些设计很不好,或者可以改进的更好的,则可以在 [论坛](https://github.com/alibaba/formily/discussions) 中提交自己的想法。 diff --git a/docs/guide/learn-formily.zh-CN.md b/docs/guide/learn-formily.zh-CN.md new file mode 100644 index 00000000000..4d56599487f --- /dev/null +++ b/docs/guide/learn-formily.zh-CN.md @@ -0,0 +1,46 @@ +# 如何学习 Formily + +## 学习建议 + +Formily 用一句话来描述,它就是一个抽象了表单领域模型的 MVVM 表单解决方案,所以,如果你想深入使用 Formily,那必须学习并了解 Formily 的领域模型到底是咋样的,它到底解决了哪些问题,了解完领域模型之后,其实就是如何消费这个领域模型的视图层了,这一层就只需要看具体组件的文档即可了。 + +## 关于文档 + +因为 Formily 的学习成本还是比较高的,想要快速了解 Formily 的全貌,最重要的还是看文档,只是文档怎么看,从哪里看会比较重要,下面我们针对不同用户给出了不同的文档学习路线。 + +### 入门级用户 + +- 引言介绍,因为你要了解 Formily 的核心思路,是否适合你的业务场景。 +- 快速开始,从最简单的例子学习实际 Formily 使用都是怎么使用的。 +- 组件文档/核心库文档,因为 Formily 为你已经封装好了大多数开箱即用的组件,遇到组件相关的问题,就像查字典一样的去查看组件文档即可。 +- 场景案例,从具体的场景出发,看看什么才是这个场景下的最佳实践。 + +### 进阶级用户 + +- 仔细消化核心概念,更深入的理解 Formily +- 进阶指南,主要学习更高级的使用方式,比如自定义组件,从简单自定义组件到超复杂自定义组件 +- 随时查阅组件文档/核心库文档,加深记忆 +- 对于自定义组件开发上的细节问题,最佳实践,推荐直接看@formily/antd 或者@formily/next 的源码,因为这就是样板代码,跟实际业务场景息息相关。 + +### 源码共建者 + +- 贡献指南,了解最基本的贡献姿势 +- 阅读文档,如果发现文档有缺陷,可以提 PR 修复 +- 阅读单元测试,了解每个测试用例所对应的实现细节,如果发现有遗漏测试用例,可以提 PR +- 阅读源码,如果发现源码有 Bug,可以提 PR + + +注意修改源码,必须要带上单元测试 + + +## 关于提问 + +如果在开发的过程中遇到问题,推荐使用文档上方的搜索功能快速搜索文档内容,快速解决,如果搜索不到的,推荐到 [论坛](https://github.com/alibaba/formily/discussions) 中提问,这里方便记录,如果遇到非常紧急的问题,可以在钉钉群里 @白玄 帮忙解决。**非常不推荐文档都不看,就直接问各种基础问题,这样很低效** + +## 关于 Bug + +如果在开发过程中发现不符合预期的行为,并能够以最小案例复现的,可以给 Formily 提[Issue](https://github.com/alibaba/formily/issues) ,非常不推荐将问题记录在 issue 里,会打乱 Issue 的信息流,同时一定注意,**提 Issue 的时候要带上最小可复现的链接地址**,方便开发者快速定位问题,快速修复,而不是在一堆代码里找 Bug。 + +## 关于 Feature Request + +如果在开发过程中发现 Formily 的某些设计很不好,或者可以改进的更好的,则可以在 [论坛](https://github.com/alibaba/formily/discussions) 中提交自己的想法。 diff --git a/docs/guide/quick-start.md b/docs/guide/quick-start.md index 5325d0a9432..9ec0d89cb05 100644 --- a/docs/guide/quick-start.md +++ b/docs/guide/quick-start.md @@ -1,50 +1,50 @@ -# 快速开始 +# Quick Start -## 安装依赖 +## Install Dependencies -### 安装内核库 +### Install the Core Library -使用 Formily 必须要用到[@formily/core](https://core.formilyjs.org),它负责管理表单的状态,表单校验,联动等等。 +To use Formily, you must use [@formily/core](https://core.formilyjs.org), which is responsible for managing the status of the form, form verification, linkage, and so on. ```bash $ npm install --save @formily/core ``` -### 安装 UI 桥接库 +### Install UI Bridge Library -单纯有了内核还不够,我们还需要一个 UI 库来接入内核数据,用来实现最终的表单交互效果,对于不同框架的用户,我们有不同的桥接库。 +The kernel alone is not enough. We also need a UI library to access kernel data to achieve the final form interaction effect. For users of different frameworks, we have different bridge libraries. -**React 用户** +**React users** ```bash $ npm install --save @formily/react ``` -**Vue 用户** +**Vue users** ```bash $ npm install --save @formily/vue ``` -### 安装组件库 +### Install component library -想要快速实现漂亮的表单,通常我们都是需要使用业界优秀的组件库的,比如[Ant Design ](https://ant.design)和 [Alibaba Fusion](https://fusion.design),但是这些优秀的组件库,在表单的某些场景上覆盖的还是不够全面,比如详情预览态的支持,Ant Design 是不支持的,还有一些场景化的组件它也是不支持的,所以 Formily 在此之上又封装了@formily/antd 和@formily/next,保证用户开箱即用。 +To quickly implement beautiful forms, we usually need to use industry-leading component libraries, such as A[nt Design](https://ant.design) and [Alibaba Fusion](https://fusion.design). However, these excellent component libraries are not fully covered in some scenes of the form. For example, the detailed preview state is not supported by Ant Design, and some scene-based components are not supported, so Formily is in On top of this, @formily/antd and @formily/next are encapsulated to ensure that users can use it out of the box. -**Ant Design 用户** +**Ant Design users** ```bash $ npm install --save antd moment @formily/antd ``` -**Alibaba Fusion 用户** +**Alibaba Fusion users** ```bash $ npm install --save @alifd/next moment @formily/next ``` -## 导入依赖 +## Import Dependencies -使用 ES Module import 语法导入依赖即可 +Use ES Module import syntax to import dependencies ```ts import React from 'react' @@ -53,7 +53,7 @@ import { FormProvider, Field } from '@formily/react' import { FormItem, Input } from '@formily/antd' ``` -## 具体用例 +## Exmaple ```tsx /** @@ -78,7 +78,7 @@ export default () => { { border: '1px dashed #666', }} > - 实时响应:{form.values.input} + Real-time response:{form.values.input} )} - 提交 + submit ) } ``` -从以上例子中,我们可以学到很多东西: - -- [createForm](https://core.formilyjs.org/api/entry/create-form)用来创建表单核心领域模型,它是作为[MVVM](https://core.formilyjs.org/guide/mvvm)设计模式的标准 ViewModel -- [FormProvider](https://react.formilyjs.org/api/components/form-provider)组件是作为视图层桥接表单模型的入口,它只有一个参数,就是接收 createForm 创建出来的 Form 实例,并将 Form 实例以上下文形式传递到子组件中 -- [FormLayout](https://antd.formilyjs.org/components/form-layout)组件是用来批量控制[FormItem](https://antd.formilyjs.org/components/form-item)样式的组件,这里我们指定布局为上下布局,也就是标签在上,组件在下 -- [Field](https://react.formilyjs.org/api/components/field)组件是用来承接普通字段的组件 - - name 属性,标识字段在表单最终提交数据中的路径 - - title 属性,标识字段的标题 - - 如果 decorator 指定为 FormItem,那么在 FormItem 组件中会默认以接收 title 属性作为标签 - - 如果指定为某个自定义组件,那么 title 的消费方则由自定义组件来承接 - - 如果不指定 decorator,那么 title 则不会显示在 UI 上 - - required 属性,必填校验的极简写法,标识该字段必填 - - 如果 decorator 指定为 FormItem,那么会自动出现星号提示,同时校验失败也会有对应的状态反馈,这些都是 FormItem 内部做的默认处理 - - 如果 decorator 指定为自定义组件,那么对应的 UI 样式则需要自定义组件实现方自己实现 - - 如果不指定 decorator,那么 required 只是会阻塞提交,校验失败不会有任何 UI 反馈。 - - initialValue 属性,代表字段的默认值 - - decorator 属性,代表字段的 UI 装饰器,通常我们都会指定为 FormItem - - 注意 decorator 属性传递的是数组形式,第一个参数代表指定组件类型,第二个参数代表指定组件属性 - - component 属性,代表字段的输入控件,可以是 Input,也可以是 Select,等等 - - 注意 component 属性传递的是数组形式,第一个参数代表指定组件类型,第二个参数代表指定组件属性 -- [FormConsumer](https://react.formilyjs.org/api/components/form-consumer)组件是作为响应式模型的响应器而存在,它核心是一个 render props 模式,在作为 children 的回调函数中,会自动收集所有依赖,如果依赖发生变化,则会重新渲染,借助 FormConsumer 我们可以很方便的实现各种计算汇总的需求 -- [FormButtonGroup](https://antd.formilyjs.org/components/form-button-group)组件作为表单按钮组容器而存在,主要负责按钮的布局 -- [Submit](https://antd.formilyjs.org/components/submit)组件作为表单提交的动作触发器而存在,其实我们也可以直接使用 form.submit 方法进行提交,但是使用 Submit 的好处是不需要每次都在 Button 组件上写 onClick 事件处理器,同时它还处理了 Form 的 loading 状态,如果 onSubmit 方法返回一个 Promise,且 Promise 正在 pending 状态,那么按钮会自动进入 loading 状态 +From the above examples, we can learn a lot: + +- [createForm](https://core.formilyjs.org/api/entry/create-form) is used to create the core domain model of the form, which is the standard ViewModel as the [MVVM](https://core.formilyjs.org/guide/mvvm) design pattern. +- The [FormProvider](https://react.formilyjs.org/api/components/form-provider) component is used as the entrance to the view layer bridge form model. It has only one parameter, which is to receive the Form instance created by createForm and pass the Form instance to the child component in the form of context. +- The [FormLayout](https://antd.formilyjs.org/components/form-layout) component is a component used to control the style of [FormItem](https://antd.formilyjs.org/components/form-item) in batches. Here we specify the layout as top and bottom layout, that is, the label is on the top and the component is on the bottom. +- The [Field](https://react.formilyjs.org/api/components/field) component is a component used to undertake common fields. + - The name attribute identifies the path of the field in the final submitted data of the form. + - Title attribute, which identifies the title of the field + - If the decorator is specified as FormItem, then the title attribute will be received as the label by default in the FormItem component. + - If specified as a custom component, the consumer of the title will be taken over by the custom component. + - If decorator is not specified, then the title will not be displayed on the UI. + - Required attribute, a shorthand for required verification, which identifies that the field is required + - If the decorator is specified as FormItem, then an asterisk prompt will automatically appear, and there will be corresponding status feedback if the verification fails. These are the default processing done inside the FormItem. + - If the decorator is specified as a custom component, the corresponding UI style needs to be implemented by the custom component implementer. + - If decorator is not specified, then required will just block submission, and there will be no UI feedback for verification failure. + - InitialValue property, which represents the default value of the field + - Decorator attribute, representing the UI decorator of the field, usually we will specify it as FormItem + - Note that the decorator attribute is passed in the form of an array, the first parameter represents the specified component type, and the second parameter represents the specified component attribute. + - The component attribute, which represents the input control of the field, can be Input or Select, etc. + - Note that the component property is passed in the form of an array, the first parameter represents the specified component type, and the second parameter represents the specified component property. +- The [FormConsumer](https://react.formilyjs.org/api/components/form-consumer) component exists as a responder of a responsive model. Its core is a render props mode. In the callback function as children, all dependencies are automatically collected. If the dependencies change, it will be re-rendered. With the help of FormConsumer, we can Conveniently realize the needs of various calculations and summaries. +- The [FormButtonGroup](https://antd.formilyjs.org/components/form-button-group) component exists as a form button group container and is mainly responsible for the layout of the buttons. +- The [Submit](https://antd.formilyjs.org/components/submit) component exists as an action trigger for form submission. In fact, we can also directly use the form.submit method to submit. But the advantage of using Submit is that there is no need to write the onClick event handler on the Button component every time, and it also handles the loading state of the Form. If the onSubmit method returns a Promise and the Promise is pending, the button will automatically enter the loading state. diff --git a/docs/guide/quick-start.zh-CN.md b/docs/guide/quick-start.zh-CN.md new file mode 100644 index 00000000000..5325d0a9432 --- /dev/null +++ b/docs/guide/quick-start.zh-CN.md @@ -0,0 +1,131 @@ +# 快速开始 + +## 安装依赖 + +### 安装内核库 + +使用 Formily 必须要用到[@formily/core](https://core.formilyjs.org),它负责管理表单的状态,表单校验,联动等等。 + +```bash +$ npm install --save @formily/core +``` + +### 安装 UI 桥接库 + +单纯有了内核还不够,我们还需要一个 UI 库来接入内核数据,用来实现最终的表单交互效果,对于不同框架的用户,我们有不同的桥接库。 + +**React 用户** + +```bash +$ npm install --save @formily/react +``` + +**Vue 用户** + +```bash +$ npm install --save @formily/vue +``` + +### 安装组件库 + +想要快速实现漂亮的表单,通常我们都是需要使用业界优秀的组件库的,比如[Ant Design ](https://ant.design)和 [Alibaba Fusion](https://fusion.design),但是这些优秀的组件库,在表单的某些场景上覆盖的还是不够全面,比如详情预览态的支持,Ant Design 是不支持的,还有一些场景化的组件它也是不支持的,所以 Formily 在此之上又封装了@formily/antd 和@formily/next,保证用户开箱即用。 + +**Ant Design 用户** + +```bash +$ npm install --save antd moment @formily/antd +``` + +**Alibaba Fusion 用户** + +```bash +$ npm install --save @alifd/next moment @formily/next +``` + +## 导入依赖 + +使用 ES Module import 语法导入依赖即可 + +```ts +import React from 'react' +import { createForm } from '@formily/core' +import { FormProvider, Field } from '@formily/react' +import { FormItem, Input } from '@formily/antd' +``` + +## 具体用例 + +```tsx +/** + * defaultShowCode: true + */ +import React from 'react' +import { createForm } from '@formily/core' +import { FormProvider, FormConsumer, Field } from '@formily/react' +import { + FormItem, + FormLayout, + Input, + FormButtonGroup, + Submit, +} from '@formily/antd' + +const form = createForm() + +export default () => { + return ( + + + + + + {() => ( +
+ 实时响应:{form.values.input} +
+ )} +
+ + 提交 + +
+ ) +} +``` + +从以上例子中,我们可以学到很多东西: + +- [createForm](https://core.formilyjs.org/api/entry/create-form)用来创建表单核心领域模型,它是作为[MVVM](https://core.formilyjs.org/guide/mvvm)设计模式的标准 ViewModel +- [FormProvider](https://react.formilyjs.org/api/components/form-provider)组件是作为视图层桥接表单模型的入口,它只有一个参数,就是接收 createForm 创建出来的 Form 实例,并将 Form 实例以上下文形式传递到子组件中 +- [FormLayout](https://antd.formilyjs.org/components/form-layout)组件是用来批量控制[FormItem](https://antd.formilyjs.org/components/form-item)样式的组件,这里我们指定布局为上下布局,也就是标签在上,组件在下 +- [Field](https://react.formilyjs.org/api/components/field)组件是用来承接普通字段的组件 + - name 属性,标识字段在表单最终提交数据中的路径 + - title 属性,标识字段的标题 + - 如果 decorator 指定为 FormItem,那么在 FormItem 组件中会默认以接收 title 属性作为标签 + - 如果指定为某个自定义组件,那么 title 的消费方则由自定义组件来承接 + - 如果不指定 decorator,那么 title 则不会显示在 UI 上 + - required 属性,必填校验的极简写法,标识该字段必填 + - 如果 decorator 指定为 FormItem,那么会自动出现星号提示,同时校验失败也会有对应的状态反馈,这些都是 FormItem 内部做的默认处理 + - 如果 decorator 指定为自定义组件,那么对应的 UI 样式则需要自定义组件实现方自己实现 + - 如果不指定 decorator,那么 required 只是会阻塞提交,校验失败不会有任何 UI 反馈。 + - initialValue 属性,代表字段的默认值 + - decorator 属性,代表字段的 UI 装饰器,通常我们都会指定为 FormItem + - 注意 decorator 属性传递的是数组形式,第一个参数代表指定组件类型,第二个参数代表指定组件属性 + - component 属性,代表字段的输入控件,可以是 Input,也可以是 Select,等等 + - 注意 component 属性传递的是数组形式,第一个参数代表指定组件类型,第二个参数代表指定组件属性 +- [FormConsumer](https://react.formilyjs.org/api/components/form-consumer)组件是作为响应式模型的响应器而存在,它核心是一个 render props 模式,在作为 children 的回调函数中,会自动收集所有依赖,如果依赖发生变化,则会重新渲染,借助 FormConsumer 我们可以很方便的实现各种计算汇总的需求 +- [FormButtonGroup](https://antd.formilyjs.org/components/form-button-group)组件作为表单按钮组容器而存在,主要负责按钮的布局 +- [Submit](https://antd.formilyjs.org/components/submit)组件作为表单提交的动作触发器而存在,其实我们也可以直接使用 form.submit 方法进行提交,但是使用 Submit 的好处是不需要每次都在 Button 组件上写 onClick 事件处理器,同时它还处理了 Form 的 loading 状态,如果 onSubmit 方法返回一个 Promise,且 Promise 正在 pending 状态,那么按钮会自动进入 loading 状态 diff --git a/docs/guide/scenes/dialog-drawer.md b/docs/guide/scenes/dialog-drawer.md index a21641fac6a..90a50104e01 100644 --- a/docs/guide/scenes/dialog-drawer.md +++ b/docs/guide/scenes/dialog-drawer.md @@ -1,3 +1,4 @@ -# 弹窗与抽屉 +# Dialog and Drawers + +Mainly use the [FormDialog](https://antd.formilyjs.org/components/form-dialog) function and [FormDrawer]() function in [@formily/antd](https://antd.formilyjs.org) or [@formily/next](https://next.formilyjs.org) -主要使用[@formily/antd](https://antd.formilyjs.org) 或 [@formily/next](https://next.formilyjs.org) 中的[FormDialog](https://antd.formilyjs.org/components/form-dialog)函数 和 [FormDrawer](https://antd.formilyjs.org/components/form-drawer)函数 diff --git a/docs/guide/scenes/dialog-drawer.zh-CN.md b/docs/guide/scenes/dialog-drawer.zh-CN.md new file mode 100644 index 00000000000..a21641fac6a --- /dev/null +++ b/docs/guide/scenes/dialog-drawer.zh-CN.md @@ -0,0 +1,3 @@ +# 弹窗与抽屉 + +主要使用[@formily/antd](https://antd.formilyjs.org) 或 [@formily/next](https://next.formilyjs.org) 中的[FormDialog](https://antd.formilyjs.org/components/form-dialog)函数 和 [FormDrawer](https://antd.formilyjs.org/components/form-drawer)函数 diff --git a/docs/guide/scenes/edit-detail.md b/docs/guide/scenes/edit-detail.md index 5636b66d3c3..ccff006f753 100644 --- a/docs/guide/scenes/edit-detail.md +++ b/docs/guide/scenes/edit-detail.md @@ -1,8 +1,8 @@ -# 编辑详情 +# Edit Details -## 编辑 +## Edit -#### Markup Schema 案例 +#### Markup Schema Cases ```tsx import React, { useState, useEffect } from 'react' @@ -42,7 +42,7 @@ const IDUpload = (props) => { authorization: 'authorization-text', }} > - + ) } @@ -120,8 +120,8 @@ export default () => { }, ], contacts: [ - { name: '张三', phone: '13245633378', email: 'zhangsan@gmail.com' }, - { name: '李四', phone: '16873452678', email: 'lisi@gmail.com' }, + { name: 'Zhang San', phone: '13245633378', email: 'zhangsan@gmail.com' }, + { name: 'Li Si', phone: '16873452678', email: 'lisi@gmail.com' }, ], }) setLoading(false) @@ -136,7 +136,7 @@ export default () => { padding: '40px 0', }} > - +
{ { x-decorator="FormItem" x-component="Input" x-component-props={{ - placeholder: '姓', + placeholder: 'firstName', }} required /> @@ -175,14 +175,14 @@ export default () => { x-decorator="FormItem" x-component="Input" x-component-props={{ - placeholder: '名', + placeholder: 'lastname', }} required /> { /> { /> { /> { /> { { /> { { - 提交 + Submit
@@ -323,7 +323,7 @@ export default () => { } ``` -#### JSON Schema 案例 +#### JSON Schema Cases ```tsx import React, { useState, useEffect } from 'react' @@ -363,7 +363,7 @@ const IDUpload = (props) => { authorization: 'authorization-text', }} > - + ) } @@ -423,14 +423,14 @@ const schema = { properties: { username: { type: 'string', - title: '用户名', + title: 'Username', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, name: { type: 'void', - title: '姓名', + title: 'Name', 'x-decorator': 'FormItem', 'x-decorator-props': { asterisk: true, @@ -444,7 +444,7 @@ const schema = { 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: '姓', + placeholder: 'firstName', }, }, lastName: { @@ -453,14 +453,14 @@ const schema = { 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: '名', + placeholder: 'lastname', }, }, }, }, email: { type: 'string', - title: '邮箱', + title: 'Email', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', @@ -468,18 +468,18 @@ const schema = { }, gender: { type: 'string', - title: '性别', + title: 'Gender', enum: [ { - label: '男', + label: 'male', value: 1, }, { - label: '女', + label: 'female', value: 2, }, { - label: '第三性别', + label: 'third gender', value: 3, }, ], @@ -489,14 +489,14 @@ const schema = { birthday: { type: 'string', required: true, - title: '生日', + title: 'Birthday', 'x-decorator': 'FormItem', 'x-component': 'DatePicker', }, address: { type: 'string', required: true, - title: '地址', + title: 'Address', 'x-decorator': 'FormItem', 'x-component': 'Cascader', 'x-reactions': '{{fetchAddress}}', @@ -504,14 +504,14 @@ const schema = { idCard: { type: 'string', required: true, - title: '身份证复印件', + title: 'ID', 'x-decorator': 'FormItem', 'x-component': 'IDUpload', }, contacts: { type: 'array', required: true, - title: '联系人信息', + title: 'Contacts', 'x-decorator': 'FormItem', 'x-component': 'ArrayItems', items: { @@ -525,7 +525,7 @@ const schema = { }, popover: { type: 'void', - title: '完善联系人信息', + title: 'Contact Informations', 'x-decorator': 'Editable.Popover', 'x-component': 'FormLayout', 'x-component-props': { @@ -543,7 +543,7 @@ const schema = { properties: { name: { type: 'string', - title: '姓名', + title: 'Name', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', @@ -555,7 +555,7 @@ const schema = { }, email: { type: 'string', - title: '邮箱', + title: 'Email', 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-validator': [{ required: true }, 'email'], @@ -567,7 +567,7 @@ const schema = { }, phone: { type: 'string', - title: '手机号', + title: 'Phone Number', 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-validator': [{ required: true }, 'phone'], @@ -589,7 +589,7 @@ const schema = { properties: { addition: { type: 'void', - title: '新增联系人', + title: 'Add Contact', 'x-component': 'ArrayItems.Addition', }, }, @@ -620,8 +620,8 @@ export default () => { }, ], contacts: [ - { name: '张三', phone: '13245633378', email: 'zhangsan@gmail.com' }, - { name: '李四', phone: '16873452678', email: 'lisi@gmail.com' }, + { name: 'Zhang San', phone: '13245633378', email: 'zhangsan@gmail.com' }, + { name: 'Li Si', phone: '16873452678', email: 'lisi@gmail.com' }, ], }) setLoading(false) @@ -636,7 +636,7 @@ export default () => { padding: '40px 0', }} > - +
{ - 提交 + Submit @@ -658,7 +658,7 @@ export default () => { } ``` -#### 纯 JSX 案例 +#### Pure JSX Cases ```tsx import React, { useState, useEffect } from 'react' @@ -698,7 +698,7 @@ const IDUpload = (props) => { authorization: 'authorization-text', }} > - + ) } @@ -760,8 +760,8 @@ export default () => { }, ], contacts: [ - { name: '张三', phone: '13245633378', email: 'zhangsan@gmail.com' }, - { name: '李四', phone: '16873452678', email: 'lisi@gmail.com' }, + { name: 'Zhang San', phone: '13245633378', email: 'zhangsan@gmail.com' }, + { name: 'Li Si', phone: '16873452678', email: 'lisi@gmail.com' }, ], }) setLoading(false) @@ -776,7 +776,7 @@ export default () => { padding: '40px 0', }} > - +
{ > { component={[ Input, { - placeholder: '姓', + placeholder: 'firstName', }, ]} required @@ -820,7 +820,7 @@ export default () => { component={[ Input, { - placeholder: '名', + placeholder: 'lastname', }, ]} required @@ -828,7 +828,7 @@ export default () => { { /> { /> { /> {(field) => ( @@ -888,7 +888,7 @@ export default () => {
{ field.title = @@ -901,7 +901,7 @@ export default () => { > { /> { /> {
))} - + )}
- 提交 + Submit @@ -969,9 +969,9 @@ export default () => { } ``` -## 详情 +## Details -#### Markup Schema 案例 +#### Markup Schema Cases ```tsx import React, { useState, useEffect } from 'react' @@ -1012,7 +1012,7 @@ const IDUpload = (props) => { authorization: 'authorization-text', }} > - {field.editable && } + {field.editable && } ) } @@ -1090,8 +1090,8 @@ export default () => { }, ], contacts: [ - { name: '张三', phone: '13245633378', email: 'zhangsan@gmail.com' }, - { name: '李四', phone: '16873452678', email: 'lisi@gmail.com' }, + { name: 'Zhang San', phone: '13245633378', email: 'zhangsan@gmail.com' }, + { name: 'Li Si', phone: '16873452678', email: 'lisi@gmail.com' }, ], }) setLoading(false) @@ -1107,7 +1107,7 @@ export default () => { }} > - +
{ { x-decorator="FormItem" x-component="Input" x-component-props={{ - placeholder: '姓', + placeholder: 'firstName', }} required /> @@ -1145,14 +1145,14 @@ export default () => { x-decorator="FormItem" x-component="Input" x-component-props={{ - placeholder: '名', + placeholder: 'lastname', }} required /> { /> { /> { /> { /> { { /> { { @@ -1289,7 +1289,7 @@ export default () => { } ``` -#### JSON Schema 案例 +#### JSON Schema Cases ```tsx import React, { useState, useEffect } from 'react' @@ -1330,7 +1330,7 @@ const IDUpload = (props) => { authorization: 'authorization-text', }} > - {field.editable && } + {field.editable && } ) } @@ -1390,14 +1390,14 @@ const schema = { properties: { username: { type: 'string', - title: '用户名', + title: 'Username', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, name: { type: 'void', - title: '姓名', + title: 'Name', 'x-decorator': 'FormItem', 'x-decorator-props': { asterisk: true, @@ -1411,7 +1411,7 @@ const schema = { 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: '姓', + placeholder: 'firstName', }, }, lastName: { @@ -1420,14 +1420,14 @@ const schema = { 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: '名', + placeholder: 'lastname', }, }, }, }, email: { type: 'string', - title: '邮箱', + title: 'Email', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', @@ -1435,18 +1435,18 @@ const schema = { }, gender: { type: 'string', - title: '性别', + title: 'Gender', enum: [ { - label: '男', + label: 'male', value: 1, }, { - label: '女', + label: 'female', value: 2, }, { - label: '第三性别', + label: 'third gender', value: 3, }, ], @@ -1456,14 +1456,14 @@ const schema = { birthday: { type: 'string', required: true, - title: '生日', + title: 'Birthday', 'x-decorator': 'FormItem', 'x-component': 'DatePicker', }, address: { type: 'string', required: true, - title: '地址', + title: 'Address', 'x-decorator': 'FormItem', 'x-component': 'Cascader', 'x-reactions': '{{fetchAddress}}', @@ -1471,14 +1471,14 @@ const schema = { idCard: { type: 'string', required: true, - title: '身份证复印件', + title: 'ID', 'x-decorator': 'FormItem', 'x-component': 'IDUpload', }, contacts: { type: 'array', required: true, - title: '联系人信息', + title: 'Contacts', 'x-decorator': 'FormItem', 'x-component': 'ArrayItems', items: { @@ -1492,7 +1492,7 @@ const schema = { }, popover: { type: 'void', - title: '完善联系人信息', + title: 'Contact Informations', 'x-decorator': 'Editable.Popover', 'x-component': 'FormLayout', 'x-component-props': { @@ -1510,7 +1510,7 @@ const schema = { properties: { name: { type: 'string', - title: '姓名', + title: 'Name', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', @@ -1522,7 +1522,7 @@ const schema = { }, email: { type: 'string', - title: '邮箱', + title: 'Email', 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-validator': [{ required: true }, 'email'], @@ -1534,7 +1534,7 @@ const schema = { }, phone: { type: 'string', - title: '手机号', + title: 'Phone Number', 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-validator': [{ required: true }, 'phone'], @@ -1556,7 +1556,7 @@ const schema = { properties: { addition: { type: 'void', - title: '新增联系人', + title: 'Add Contact', 'x-component': 'ArrayItems.Addition', }, }, @@ -1587,8 +1587,8 @@ export default () => { }, ], contacts: [ - { name: '张三', phone: '13245633378', email: 'zhangsan@gmail.com' }, - { name: '李四', phone: '16873452678', email: 'lisi@gmail.com' }, + { name: 'Zhang San', phone: '13245633378', email: 'zhangsan@gmail.com' }, + { name: 'Li Si', phone: '16873452678', email: 'lisi@gmail.com' }, ], }) setLoading(false) @@ -1604,7 +1604,7 @@ export default () => { }} > - + { } ``` -#### 纯 JSX 案例 +#### Pure JSX Cases ```tsx import React, { useState, useEffect } from 'react' @@ -1664,7 +1664,7 @@ const IDUpload = (props) => { authorization: 'authorization-text', }} > - {field.editable && } + {field.editable && } ) } @@ -1726,8 +1726,8 @@ export default () => { }, ], contacts: [ - { name: '张三', phone: '13245633378', email: 'zhangsan@gmail.com' }, - { name: '李四', phone: '16873452678', email: 'lisi@gmail.com' }, + { name: 'Zhang San', phone: '13245633378', email: 'zhangsan@gmail.com' }, + { name: 'Li Si', phone: '16873452678', email: 'lisi@gmail.com' }, ], }) setLoading(false) @@ -1743,7 +1743,7 @@ export default () => { }} > - + { > { component={[ Input, { - placeholder: '姓', + placeholder: 'firstName', }, ]} required @@ -1786,7 +1786,7 @@ export default () => { component={[ Input, { - placeholder: '名', + placeholder: 'lastname', }, ]} required @@ -1794,7 +1794,7 @@ export default () => { { /> { /> { /> {(field) => ( @@ -1854,7 +1854,7 @@ export default () => {
{ field.title = @@ -1867,7 +1867,7 @@ export default () => { > { /> { /> {
))} - + )}
diff --git a/docs/guide/scenes/edit-detail.zh-CN.md b/docs/guide/scenes/edit-detail.zh-CN.md new file mode 100644 index 00000000000..5636b66d3c3 --- /dev/null +++ b/docs/guide/scenes/edit-detail.zh-CN.md @@ -0,0 +1,1927 @@ +# 编辑详情 + +## 编辑 + +#### Markup Schema 案例 + +```tsx +import React, { useState, useEffect } from 'react' +import { createForm } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { + Form, + FormItem, + FormLayout, + Input, + Select, + Cascader, + DatePicker, + Submit, + FormGrid, + Upload, + ArrayItems, + Editable, + FormButtonGroup, +} from '@formily/antd' +import { action } from '@formily/reactive' +import { Card, Button, Spin } from 'antd' +import { UploadOutlined } from '@ant-design/icons' +import 'antd/lib/tabs/style' +import 'antd/lib/button/style' + +const form = createForm({ + validateFirst: true, +}) + +const IDUpload = (props) => { + return ( + + + + ) +} + +const SchemaField = createSchemaField({ + components: { + FormItem, + FormGrid, + FormLayout, + Input, + DatePicker, + Cascader, + Select, + IDUpload, + ArrayItems, + Editable, + }, + scope: { + fetchAddress: (field) => { + const transform = (data = {}) => { + return Object.entries(data).reduce((buf, [key, value]) => { + if (typeof value === 'string') + return buf.concat({ + label: value, + value: key, + }) + const { name, code, cities, districts } = value + const _cities = transform(cities) + const _districts = transform(districts) + return buf.concat({ + label: name, + value: code, + children: _cities.length + ? _cities + : _districts.length + ? _districts + : undefined, + }) + }, []) + } + + field.loading = true + fetch('//unpkg.com/china-location/dist/location.json') + .then((res) => res.json()) + .then( + action((data) => { + field.dataSource = transform(data) + field.loading = false + }) + ) + }, + }, +}) + +export default () => { + const [loading, setLoading] = useState(true) + useEffect(() => { + setTimeout(() => { + form.setInitialValues({ + username: 'Aston Martin', + firstName: 'Aston', + lastName: 'Martin', + email: 'aston_martin@aston.com', + gender: 1, + birthday: '1836-01-03', + address: ['110000', '110000', '110101'], + idCard: [ + { + name: 'this is image', + thumbUrl: + 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', + uid: 'rc-upload-1615825692847-2', + url: + 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', + }, + ], + contacts: [ + { name: '张三', phone: '13245633378', email: 'zhangsan@gmail.com' }, + { name: '李四', phone: '16873452678', email: 'lisi@gmail.com' }, + ], + }) + setLoading(false) + }, 2000) + }, []) + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 提交 + + + + + +
+ ) +} +``` + +#### JSON Schema 案例 + +```tsx +import React, { useState, useEffect } from 'react' +import { createForm } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { + Form, + FormItem, + FormLayout, + Input, + Select, + Cascader, + DatePicker, + Submit, + FormGrid, + Upload, + ArrayItems, + Editable, + FormButtonGroup, +} from '@formily/antd' +import { action } from '@formily/reactive' +import { Card, Button, Spin } from 'antd' +import { UploadOutlined } from '@ant-design/icons' +import 'antd/lib/tabs/style' +import 'antd/lib/button/style' + +const form = createForm({ + validateFirst: true, +}) + +const IDUpload = (props) => { + return ( + + + + ) +} + +const SchemaField = createSchemaField({ + components: { + FormItem, + FormGrid, + FormLayout, + Input, + DatePicker, + Cascader, + Select, + IDUpload, + ArrayItems, + Editable, + }, + scope: { + fetchAddress: (field) => { + const transform = (data = {}) => { + return Object.entries(data).reduce((buf, [key, value]) => { + if (typeof value === 'string') + return buf.concat({ + label: value, + value: key, + }) + const { name, code, cities, districts } = value + const _cities = transform(cities) + const _districts = transform(districts) + return buf.concat({ + label: name, + value: code, + children: _cities.length + ? _cities + : _districts.length + ? _districts + : undefined, + }) + }, []) + } + + field.loading = true + fetch('//unpkg.com/china-location/dist/location.json') + .then((res) => res.json()) + .then( + action((data) => { + field.dataSource = transform(data) + field.loading = false + }) + ) + }, + }, +}) + +const schema = { + type: 'object', + properties: { + username: { + type: 'string', + title: '用户名', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + name: { + type: 'void', + title: '姓名', + 'x-decorator': 'FormItem', + 'x-decorator-props': { + asterisk: true, + feedbackLayout: 'none', + }, + 'x-component': 'FormGrid', + properties: { + firstName: { + type: 'string', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-component-props': { + placeholder: '姓', + }, + }, + lastName: { + type: 'string', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-component-props': { + placeholder: '名', + }, + }, + }, + }, + email: { + type: 'string', + title: '邮箱', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-validator': 'email', + }, + gender: { + type: 'string', + title: '性别', + enum: [ + { + label: '男', + value: 1, + }, + { + label: '女', + value: 2, + }, + { + label: '第三性别', + value: 3, + }, + ], + 'x-decorator': 'FormItem', + 'x-component': 'Select', + }, + birthday: { + type: 'string', + required: true, + title: '生日', + 'x-decorator': 'FormItem', + 'x-component': 'DatePicker', + }, + address: { + type: 'string', + required: true, + title: '地址', + 'x-decorator': 'FormItem', + 'x-component': 'Cascader', + 'x-reactions': '{{fetchAddress}}', + }, + idCard: { + type: 'string', + required: true, + title: '身份证复印件', + 'x-decorator': 'FormItem', + 'x-component': 'IDUpload', + }, + contacts: { + type: 'array', + required: true, + title: '联系人信息', + 'x-decorator': 'FormItem', + 'x-component': 'ArrayItems', + items: { + type: 'object', + 'x-component': 'ArrayItems.Item', + properties: { + sort: { + type: 'void', + 'x-decorator': 'FormItem', + 'x-component': 'ArrayItems.SortHandle', + }, + popover: { + type: 'void', + title: '完善联系人信息', + 'x-decorator': 'Editable.Popover', + 'x-component': 'FormLayout', + 'x-component-props': { + layout: 'vertical', + }, + 'x-reactions': [ + { + fulfill: { + schema: { + title: '{{$self.query(".name").value() }}', + }, + }, + }, + ], + properties: { + name: { + type: 'string', + title: '姓名', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-component-props': { + style: { + width: 300, + }, + }, + }, + email: { + type: 'string', + title: '邮箱', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-validator': [{ required: true }, 'email'], + 'x-component-props': { + style: { + width: 300, + }, + }, + }, + phone: { + type: 'string', + title: '手机号', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-validator': [{ required: true }, 'phone'], + 'x-component-props': { + style: { + width: 300, + }, + }, + }, + }, + }, + remove: { + type: 'void', + 'x-decorator': 'FormItem', + 'x-component': 'ArrayItems.Remove', + }, + }, + }, + properties: { + addition: { + type: 'void', + title: '新增联系人', + 'x-component': 'ArrayItems.Addition', + }, + }, + }, + }, +} + +export default () => { + const [loading, setLoading] = useState(true) + useEffect(() => { + setTimeout(() => { + form.setInitialValues({ + username: 'Aston Martin', + firstName: 'Aston', + lastName: 'Martin', + email: 'aston_martin@aston.com', + gender: 1, + birthday: '1836-01-03', + address: ['110000', '110000', '110101'], + idCard: [ + { + name: 'this is image', + thumbUrl: + 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', + uid: 'rc-upload-1615825692847-2', + url: + 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', + }, + ], + contacts: [ + { name: '张三', phone: '13245633378', email: 'zhangsan@gmail.com' }, + { name: '李四', phone: '16873452678', email: 'lisi@gmail.com' }, + ], + }) + setLoading(false) + }, 2000) + }, []) + return ( +
+ + +
+ + + + 提交 + + + +
+
+
+ ) +} +``` + +#### 纯 JSX 案例 + +```tsx +import React, { useState, useEffect } from 'react' +import { createForm } from '@formily/core' +import { Field, VoidField, ArrayField } from '@formily/react' +import { + Form, + FormItem, + FormLayout, + Input, + Select, + Cascader, + DatePicker, + Submit, + FormGrid, + Upload, + ArrayBase, + Editable, + FormButtonGroup, +} from '@formily/antd' +import { action } from '@formily/reactive' +import { Card, Button, Spin } from 'antd' +import { UploadOutlined } from '@ant-design/icons' +import 'antd/lib/tabs/style' +import 'antd/lib/button/style' + +const form = createForm({ + validateFirst: true, +}) + +const IDUpload = (props) => { + return ( + + + + ) +} + +const fetchAddress = (field) => { + const transform = (data = {}) => { + return Object.entries(data).reduce((buf, [key, value]) => { + if (typeof value === 'string') + return buf.concat({ + label: value, + value: key, + }) + const { name, code, cities, districts } = value + const _cities = transform(cities) + const _districts = transform(districts) + return buf.concat({ + label: name, + value: code, + children: _cities.length + ? _cities + : _districts.length + ? _districts + : undefined, + }) + }, []) + } + + field.loading = true + fetch('//unpkg.com/china-location/dist/location.json') + .then((res) => res.json()) + .then( + action((data) => { + field.dataSource = transform(data) + field.loading = false + }) + ) +} + +export default () => { + const [loading, setLoading] = useState(true) + useEffect(() => { + setTimeout(() => { + form.setInitialValues({ + username: 'Aston Martin', + firstName: 'Aston', + lastName: 'Martin', + email: 'aston_martin@aston.com', + gender: 1, + birthday: '1836-01-03', + address: ['110000', '110000', '110101'], + idCard: [ + { + name: 'this is image', + thumbUrl: + 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', + uid: 'rc-upload-1615825692847-2', + url: + 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', + }, + ], + contacts: [ + { name: '张三', phone: '13245633378', email: 'zhangsan@gmail.com' }, + { name: '李四', phone: '16873452678', email: 'lisi@gmail.com' }, + ], + }) + setLoading(false) + }, 2000) + }, []) + return ( +
+ + +
+ + + + + + + + + + + + {(field) => ( + + {field.value?.map((item, index) => ( +
+ { + field.title = + field.query('.[].name').value() || field.title + }} + > + + + + + + + + + + + +
+ ))} + +
+ )} +
+ + + 提交 + + + +
+
+
+ ) +} +``` + +## 详情 + +#### Markup Schema 案例 + +```tsx +import React, { useState, useEffect } from 'react' +import { createForm } from '@formily/core' +import { createSchemaField, useField } from '@formily/react' +import { + Form, + FormItem, + FormLayout, + Input, + Select, + Cascader, + DatePicker, + FormGrid, + Upload, + ArrayItems, + Editable, + PreviewText, +} from '@formily/antd' +import { action } from '@formily/reactive' +import { Card, Button, Spin } from 'antd' +import { UploadOutlined } from '@ant-design/icons' +import 'antd/lib/tabs/style' +import 'antd/lib/button/style' + +const form = createForm({ + readPretty: true, + validateFirst: true, +}) + +const IDUpload = (props) => { + const field = useField() + return ( + + {field.editable && } + + ) +} + +const SchemaField = createSchemaField({ + components: { + FormItem, + FormGrid, + FormLayout, + Input, + DatePicker, + Cascader, + Select, + IDUpload, + ArrayItems, + Editable, + }, + scope: { + fetchAddress: (field) => { + const transform = (data = {}) => { + return Object.entries(data).reduce((buf, [key, value]) => { + if (typeof value === 'string') + return buf.concat({ + label: value, + value: key, + }) + const { name, code, cities, districts } = value + const _cities = transform(cities) + const _districts = transform(districts) + return buf.concat({ + label: name, + value: code, + children: _cities.length + ? _cities + : _districts.length + ? _districts + : undefined, + }) + }, []) + } + + field.loading = true + fetch('//unpkg.com/china-location/dist/location.json') + .then((res) => res.json()) + .then( + action((data) => { + field.dataSource = transform(data) + field.loading = false + }) + ) + }, + }, +}) + +export default () => { + const [loading, setLoading] = useState(true) + useEffect(() => { + setTimeout(() => { + form.setInitialValues({ + username: 'Aston Martin', + firstName: 'Aston', + lastName: 'Martin', + email: 'aston_martin@aston.com', + gender: 1, + birthday: '1836-01-03', + address: ['110000', '110000', '110101'], + idCard: [ + { + name: 'this is image', + thumbUrl: + 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', + uid: 'rc-upload-1615825692847-2', + url: + 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', + }, + ], + contacts: [ + { name: '张三', phone: '13245633378', email: 'zhangsan@gmail.com' }, + { name: '李四', phone: '16873452678', email: 'lisi@gmail.com' }, + ], + }) + setLoading(false) + }, 2000) + }, []) + return ( +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ ) +} +``` + +#### JSON Schema 案例 + +```tsx +import React, { useState, useEffect } from 'react' +import { createForm } from '@formily/core' +import { createSchemaField, useField } from '@formily/react' +import { + Form, + FormItem, + FormLayout, + Input, + Select, + Cascader, + DatePicker, + FormGrid, + Upload, + ArrayItems, + Editable, + PreviewText, +} from '@formily/antd' +import { action } from '@formily/reactive' +import { Card, Button, Spin } from 'antd' +import { UploadOutlined } from '@ant-design/icons' +import 'antd/lib/tabs/style' +import 'antd/lib/button/style' + +const form = createForm({ + readPretty: true, + validateFirst: true, +}) + +const IDUpload = (props) => { + const field = useField() + return ( + + {field.editable && } + + ) +} + +const SchemaField = createSchemaField({ + components: { + FormItem, + FormGrid, + FormLayout, + Input, + DatePicker, + Cascader, + Select, + IDUpload, + ArrayItems, + Editable, + }, + scope: { + fetchAddress: (field) => { + const transform = (data = {}) => { + return Object.entries(data).reduce((buf, [key, value]) => { + if (typeof value === 'string') + return buf.concat({ + label: value, + value: key, + }) + const { name, code, cities, districts } = value + const _cities = transform(cities) + const _districts = transform(districts) + return buf.concat({ + label: name, + value: code, + children: _cities.length + ? _cities + : _districts.length + ? _districts + : undefined, + }) + }, []) + } + + field.loading = true + fetch('//unpkg.com/china-location/dist/location.json') + .then((res) => res.json()) + .then( + action((data) => { + field.dataSource = transform(data) + field.loading = false + }) + ) + }, + }, +}) + +const schema = { + type: 'object', + properties: { + username: { + type: 'string', + title: '用户名', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + name: { + type: 'void', + title: '姓名', + 'x-decorator': 'FormItem', + 'x-decorator-props': { + asterisk: true, + feedbackLayout: 'none', + }, + 'x-component': 'FormGrid', + properties: { + firstName: { + type: 'string', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-component-props': { + placeholder: '姓', + }, + }, + lastName: { + type: 'string', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-component-props': { + placeholder: '名', + }, + }, + }, + }, + email: { + type: 'string', + title: '邮箱', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-validator': 'email', + }, + gender: { + type: 'string', + title: '性别', + enum: [ + { + label: '男', + value: 1, + }, + { + label: '女', + value: 2, + }, + { + label: '第三性别', + value: 3, + }, + ], + 'x-decorator': 'FormItem', + 'x-component': 'Select', + }, + birthday: { + type: 'string', + required: true, + title: '生日', + 'x-decorator': 'FormItem', + 'x-component': 'DatePicker', + }, + address: { + type: 'string', + required: true, + title: '地址', + 'x-decorator': 'FormItem', + 'x-component': 'Cascader', + 'x-reactions': '{{fetchAddress}}', + }, + idCard: { + type: 'string', + required: true, + title: '身份证复印件', + 'x-decorator': 'FormItem', + 'x-component': 'IDUpload', + }, + contacts: { + type: 'array', + required: true, + title: '联系人信息', + 'x-decorator': 'FormItem', + 'x-component': 'ArrayItems', + items: { + type: 'object', + 'x-component': 'ArrayItems.Item', + properties: { + sort: { + type: 'void', + 'x-decorator': 'FormItem', + 'x-component': 'ArrayItems.SortHandle', + }, + popover: { + type: 'void', + title: '完善联系人信息', + 'x-decorator': 'Editable.Popover', + 'x-component': 'FormLayout', + 'x-component-props': { + layout: 'vertical', + }, + 'x-reactions': [ + { + fulfill: { + schema: { + title: '{{$self.query(".name").value() }}', + }, + }, + }, + ], + properties: { + name: { + type: 'string', + title: '姓名', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-component-props': { + style: { + width: 300, + }, + }, + }, + email: { + type: 'string', + title: '邮箱', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-validator': [{ required: true }, 'email'], + 'x-component-props': { + style: { + width: 300, + }, + }, + }, + phone: { + type: 'string', + title: '手机号', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-validator': [{ required: true }, 'phone'], + 'x-component-props': { + style: { + width: 300, + }, + }, + }, + }, + }, + remove: { + type: 'void', + 'x-decorator': 'FormItem', + 'x-component': 'ArrayItems.Remove', + }, + }, + }, + properties: { + addition: { + type: 'void', + title: '新增联系人', + 'x-component': 'ArrayItems.Addition', + }, + }, + }, + }, +} + +export default () => { + const [loading, setLoading] = useState(true) + useEffect(() => { + setTimeout(() => { + form.setInitialValues({ + username: 'Aston Martin', + firstName: 'Aston', + lastName: 'Martin', + email: 'aston_martin@aston.com', + gender: 1, + birthday: '1836-01-03', + address: ['110000', '110000', '110101'], + idCard: [ + { + name: 'this is image', + thumbUrl: + 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', + uid: 'rc-upload-1615825692847-2', + url: + 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', + }, + ], + contacts: [ + { name: '张三', phone: '13245633378', email: 'zhangsan@gmail.com' }, + { name: '李四', phone: '16873452678', email: 'lisi@gmail.com' }, + ], + }) + setLoading(false) + }, 2000) + }, []) + return ( +
+ + + +
+ + +
+
+
+
+ ) +} +``` + +#### 纯 JSX 案例 + +```tsx +import React, { useState, useEffect } from 'react' +import { createForm } from '@formily/core' +import { Field, VoidField, ArrayField, useField } from '@formily/react' +import { + Form, + FormItem, + FormLayout, + Input, + Select, + Cascader, + DatePicker, + FormGrid, + ArrayBase, + Upload, + PreviewText, + Editable, +} from '@formily/antd' +import { action } from '@formily/reactive' +import { Card, Button, Spin } from 'antd' +import { UploadOutlined } from '@ant-design/icons' +import 'antd/lib/tabs/style' +import 'antd/lib/button/style' +import './index.less' + +const form = createForm({ + validateFirst: true, + readPretty: true, +}) + +const IDUpload = (props) => { + const field = useField() + return ( + + {field.editable && } + + ) +} + +const fetchAddress = (field) => { + const transform = (data = {}) => { + return Object.entries(data).reduce((buf, [key, value]) => { + if (typeof value === 'string') + return buf.concat({ + label: value, + value: key, + }) + const { name, code, cities, districts } = value + const _cities = transform(cities) + const _districts = transform(districts) + return buf.concat({ + label: name, + value: code, + children: _cities.length + ? _cities + : _districts.length + ? _districts + : undefined, + }) + }, []) + } + + field.loading = true + fetch('//unpkg.com/china-location/dist/location.json') + .then((res) => res.json()) + .then( + action((data) => { + field.dataSource = transform(data) + field.loading = false + }) + ) +} + +export default () => { + const [loading, setLoading] = useState(true) + useEffect(() => { + setTimeout(() => { + form.setInitialValues({ + username: 'Aston Martin', + firstName: 'Aston', + lastName: 'Martin', + email: 'aston_martin@aston.com', + gender: 1, + birthday: '1836-01-03', + address: ['110000', '110000', '110101'], + idCard: [ + { + name: 'this is image', + thumbUrl: + 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', + uid: 'rc-upload-1615825692847-2', + url: + 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', + }, + ], + contacts: [ + { name: '张三', phone: '13245633378', email: 'zhangsan@gmail.com' }, + { name: '李四', phone: '16873452678', email: 'lisi@gmail.com' }, + ], + }) + setLoading(false) + }, 2000) + }, []) + return ( +
+ + + +
+ + + + + + + + + + + + {(field) => ( + + {field.value?.map((item, index) => ( +
+ { + field.title = + field.query('.[].name').value() || field.title + }} + > + + + + + + +
+ ))} + +
+ )} +
+ +
+
+
+
+ ) +} +``` diff --git a/docs/guide/scenes/login-register.md b/docs/guide/scenes/login-register.md index 19c2f25c1c5..7477d504617 100644 --- a/docs/guide/scenes/login-register.md +++ b/docs/guide/scenes/login-register.md @@ -1,8 +1,8 @@ -# 登陆注册 +# Log in&Sign up -## 登陆 +## Log in -#### Markup Schema 案例 +#### Markup Schema Cases ```tsx import React from 'react' @@ -49,7 +49,7 @@ export default () => { > - +
{ { /> { /> - 登陆 + Log in
- +
{ { /> { /> - 登陆 + Log in
@@ -139,8 +139,8 @@ export default () => { justifyContent: 'space-between', }} > - 新用户注册 - 忘记密码? + new user registration + forgot password?
@@ -148,7 +148,7 @@ export default () => { } ``` -#### JSON Schema 案例 +#### JSON Schema Cases ```tsx import React from 'react' @@ -188,7 +188,7 @@ const normalSchema = { properties: { username: { type: 'string', - title: '用户名', + title: 'Username', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', @@ -198,7 +198,7 @@ const normalSchema = { }, password: { type: 'string', - title: '密码', + title: 'Password', required: true, 'x-decorator': 'FormItem', 'x-component': 'Password', @@ -214,7 +214,7 @@ const phoneSchema = { properties: { phone: { type: 'string', - title: '手机号', + title: 'Phone Number', required: true, 'x-validator': 'phone', 'x-decorator': 'FormItem', @@ -225,7 +225,7 @@ const phoneSchema = { }, verifyCode: { type: 'string', - title: '验证码', + title: 'Verification Code', required: true, 'x-decorator': 'FormItem', 'x-component': 'VerifyCode', @@ -259,7 +259,7 @@ export default () => { > - +
{ > - 登陆 + Log in
- +
{ > - 登陆 + Log in
@@ -292,8 +292,8 @@ export default () => { justifyContent: 'space-between', }} > - 新用户注册 - 忘记密码? + new user registration + forgot password?
@@ -301,7 +301,7 @@ export default () => { } ``` -#### 纯 JSX 案例 +#### Pure JSX Cases ```tsx import React from 'react' @@ -334,7 +334,7 @@ export default () => { > - +
{ > { /> { ]} /> - 登陆 + Log in
- +
{ > { /> { const phone = field.query('.phone') @@ -410,7 +410,7 @@ export default () => { ]} /> - 登陆 + Log in
@@ -421,8 +421,8 @@ export default () => { justifyContent: 'space-between', }} > - 新用户注册 - 忘记密码? + new user registration + 忘记password?
@@ -430,9 +430,9 @@ export default () => { } ``` -## 新用户注册 +## New User Registration -#### Markup Schema 案例 +#### Markup Schema Cases ```tsx import React from 'react' @@ -474,7 +474,7 @@ const IDUpload = (props) => { authorization: 'authorization-text', }} > - + ) } @@ -541,7 +541,7 @@ export default () => { padding: '40px 0', }} > - +
{ { fulfill: { state: { errors: - '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "确认密码不匹配" : ""}}', + '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "Confirm that the password does not match" : ""}}', }, }, }, @@ -579,7 +579,7 @@ export default () => { /> { fulfill: { state: { errors: - '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "确认密码不匹配" : ""}}', + '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "Confirm that the password does not match" : ""}}', }, }, }, ]} /> { x-decorator="FormItem" x-component="Input" x-component-props={{ - placeholder: '姓', + placeholder: 'firstname', }} required /> @@ -621,14 +621,14 @@ export default () => { x-decorator="FormItem" x-component="Input" x-component-props={{ - placeholder: '名', + placeholder: 'lastname', }} required /> { /> { /> { /> { /> { { /> { { - 注册 + Sign up
@@ -769,7 +769,7 @@ export default () => { } ``` -#### JSON Schema 案例 +#### JSON Schema Cases ```tsx import React from 'react' @@ -873,14 +873,14 @@ const schema = { properties: { username: { type: 'string', - title: '用户名', + title: 'Username', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, password: { type: 'string', - title: '密码', + title: 'Password', required: true, 'x-decorator': 'FormItem', 'x-component': 'Password', @@ -893,7 +893,7 @@ const schema = { fulfill: { state: { errors: - '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "确认密码不匹配" : ""}}', + '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "确认password不匹配" : ""}}', }, }, }, @@ -901,7 +901,7 @@ const schema = { }, confirm_password: { type: 'string', - title: '确认密码', + title: 'Confirm Password', required: true, 'x-decorator': 'FormItem', 'x-component': 'Password', @@ -914,7 +914,7 @@ const schema = { fulfill: { state: { errors: - '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "确认密码不匹配" : ""}}', + '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "Confirm that the password does not match" : ""}}', }, }, }, @@ -922,7 +922,7 @@ const schema = { }, name: { type: 'void', - title: '姓名', + title: 'name', 'x-decorator': 'FormItem', 'x-decorator-props': { asterisk: true, @@ -936,7 +936,7 @@ const schema = { 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: '姓', + placeholder: 'firstname', }, }, lastName: { @@ -945,14 +945,14 @@ const schema = { 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: '名', + placeholder: 'lastname', }, }, }, }, email: { type: 'string', - title: '邮箱', + title: 'Email', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', @@ -960,18 +960,18 @@ const schema = { }, gender: { type: 'string', - title: '性别', + title: 'Gender', enum: [ { - label: '男', + label: 'male', value: 1, }, { - label: '女', + label: 'female', value: 2, }, { - label: '第三性别', + label: 'third gender', value: 3, }, ], @@ -981,14 +981,14 @@ const schema = { birthday: { type: 'string', required: true, - title: '生日', + title: 'Birthday', 'x-decorator': 'FormItem', 'x-component': 'DatePicker', }, address: { type: 'string', required: true, - title: '地址', + title: 'Address', 'x-decorator': 'FormItem', 'x-component': 'Cascader', 'x-reactions': '{{fetchAddress}}', @@ -996,14 +996,14 @@ const schema = { idCard: { type: 'string', required: true, - title: '身份证复印件', + title: 'ID', 'x-decorator': 'FormItem', 'x-component': 'IDUpload', }, contacts: { type: 'array', required: true, - title: '联系人信息', + title: 'Contacts', 'x-decorator': 'FormItem', 'x-component': 'ArrayItems', items: { @@ -1017,7 +1017,7 @@ const schema = { }, popover: { type: 'void', - title: '完善联系人信息', + title: 'improve contact information', 'x-decorator': 'Editable.Popover', 'x-component': 'FormLayout', 'x-component-props': { @@ -1036,7 +1036,7 @@ const schema = { properties: { name: { type: 'string', - title: '姓名', + title: 'Name', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', @@ -1048,7 +1048,7 @@ const schema = { }, email: { type: 'string', - title: '邮箱', + title: 'Email', 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-validator': [{ required: true }, 'email'], @@ -1060,7 +1060,7 @@ const schema = { }, phone: { type: 'string', - title: '手机号', + title: 'Phone Number', 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-validator': [{ required: true }, 'phone'], @@ -1082,7 +1082,7 @@ const schema = { properties: { addition: { type: 'void', - title: '新增联系人', + title: 'Add Contact', 'x-component': 'ArrayItems.Addition', }, }, @@ -1100,7 +1100,7 @@ export default () => { padding: '40px 0', }} > - +
{ - 注册 + Sign up @@ -1120,7 +1120,7 @@ export default () => { } ``` -#### 纯 JSX 案例 +#### Pure JSX Cases ```tsx import React from 'react' @@ -1158,7 +1158,7 @@ const IDUpload = (props) => { authorization: 'authorization-text', }} > - + ) } @@ -1173,7 +1173,7 @@ export default () => { padding: '40px 0', }} > - +
{ > { confirm.get('value') && field.value && field.value !== confirm.get('value') - ? '确认密码不匹配' + ? 'Confirm that the password does not match' : '' }} /> { password.get('value') && field.value && field.value !== password.get('value') - ? '确认密码不匹配' + ? 'Confirm that the password does not match' : '' }} /> { component={[ Input, { - placeholder: '姓', + placeholder: 'firstname', }, ]} required @@ -1258,7 +1258,7 @@ export default () => { component={[ Input, { - placeholder: '名', + placeholder: 'lastname', }, ]} required @@ -1266,7 +1266,7 @@ export default () => { { /> { /> { /> - 注册 + Sign up @@ -1359,9 +1359,9 @@ export default () => { } ``` -## 忘记密码 +## Forgot password -#### Markup Schema 案例 +#### Markup Schema Cases ```tsx import React from 'react' @@ -1400,7 +1400,7 @@ export default () => { padding: '40px 0', }} > - +
{ { /> { fulfill: { state: { errors: - '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "确认密码不匹配" : ""}}', + '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "Confirm that the password does not match" : ""}}', }, }, }, @@ -1453,7 +1453,7 @@ export default () => { /> { fulfill: { state: { errors: - '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "确认密码不匹配" : ""}}', + '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "Confirm that the password does not match" : ""}}', }, }, }, @@ -1475,7 +1475,7 @@ export default () => { - 确认变更 + Confirm
@@ -1485,7 +1485,7 @@ export default () => { } ``` -#### JSON Schema 案例 +#### JSON Schema Cases ```tsx import React from 'react' @@ -1519,14 +1519,14 @@ const schema = { properties: { username: { type: 'string', - title: '用户名', + title: 'Username', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, email: { type: 'string', - title: '邮箱', + title: 'Email', required: true, 'x-validator': 'email', 'x-decorator': 'FormItem', @@ -1534,14 +1534,14 @@ const schema = { }, oldPassword: { type: 'string', - title: '原始密码', + title: 'Old Password', required: true, 'x-decorator': 'FormItem', 'x-component': 'Password', }, password: { type: 'string', - title: '新密码', + title: 'New Password', required: true, 'x-decorator': 'FormItem', 'x-component': 'Password', @@ -1554,7 +1554,7 @@ const schema = { fulfill: { state: { errors: - '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "确认密码不匹配" : ""}}', + '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "Confirm that the password does not match" : ""}}', }, }, }, @@ -1562,7 +1562,7 @@ const schema = { }, confirm_password: { type: 'string', - title: '确认密码', + title: 'Confirm Password', required: true, 'x-decorator': 'FormItem', 'x-component': 'Password', @@ -1575,7 +1575,7 @@ const schema = { fulfill: { state: { errors: - '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "确认密码不匹配" : ""}}', + '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "Confirm that the password does not match" : ""}}', }, }, }, @@ -1594,7 +1594,7 @@ export default () => { padding: '40px 0', }} > - +
{ - 确认变更 + Confirm @@ -1614,7 +1614,7 @@ export default () => { } ``` -#### 纯 JSX 案例 +#### Pure JSX Cases ```tsx import React from 'react' @@ -1645,7 +1645,7 @@ export default () => { padding: '40px 0', }} > - +
{ > { /> { confirm.get('value') && field.value && field.value !== confirm.get('value') - ? '确认密码不匹配' + ? 'Confirm that the password does not match' : '' }} /> { confirm.get('value') && field.value && field.value !== confirm.get('value') - ? '确认密码不匹配' + ? 'Confirm that the password does not match' : '' }} /> - 确认变更 + Confirm change diff --git a/docs/guide/scenes/login-register.zh-CN.md b/docs/guide/scenes/login-register.zh-CN.md new file mode 100644 index 00000000000..19c2f25c1c5 --- /dev/null +++ b/docs/guide/scenes/login-register.zh-CN.md @@ -0,0 +1,1729 @@ +# 登陆注册 + +## 登陆 + +#### Markup Schema 案例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { Form, FormItem, Input, Password, Submit } from '@formily/antd' +import { Tabs, Card } from 'antd' +import * as ICONS from '@ant-design/icons' +import { VerifyCode } from './VerifyCode' +import 'antd/lib/tabs/style' +import 'antd/lib/button/style' + +const normalForm = createForm({ + validateFirst: true, +}) + +const phoneForm = createForm({ + validateFirst: true, +}) + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + Password, + VerifyCode, + }, + scope: { + icon(name) { + return React.createElement(ICONS[name]) + }, + }, +}) + +export default () => { + return ( +
+ + + +
+ + + + + + 登陆 + +
+
+ +
+ + + + + + 登陆 + +
+
+
+ +
+
+ ) +} +``` + +#### JSON Schema 案例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { Form, FormItem, Input, Password, Submit } from '@formily/antd' +import { Tabs, Card } from 'antd' +import * as ICONS from '@ant-design/icons' +import { VerifyCode } from './VerifyCode' +import 'antd/lib/tabs/style' +import 'antd/lib/button/style' + +const normalForm = createForm({ + validateFirst: true, +}) + +const phoneForm = createForm({ + validateFirst: true, +}) + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + Password, + VerifyCode, + }, + scope: { + icon(name) { + return React.createElement(ICONS[name]) + }, + }, +}) + +const normalSchema = { + type: 'object', + properties: { + username: { + type: 'string', + title: '用户名', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-component-props': { + prefix: "{{icon('UserOutlined')}}", + }, + }, + password: { + type: 'string', + title: '密码', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Password', + 'x-component-props': { + prefix: "{{icon('LockOutlined')}}", + }, + }, + }, +} + +const phoneSchema = { + type: 'object', + properties: { + phone: { + type: 'string', + title: '手机号', + required: true, + 'x-validator': 'phone', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-component-props': { + prefix: "{{icon('PhoneOutlined')}}", + }, + }, + verifyCode: { + type: 'string', + title: '验证码', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'VerifyCode', + 'x-component-props': { + prefix: "{{icon('LockOutlined')}}", + }, + 'x-reactions': [ + { + dependencies: ['.phone#value', '.phone#valid'], + fulfill: { + state: { + 'component[1].readyPost': '{{$deps[0] && $deps[1]}}', + 'component[1].phoneNumber': '{{$deps[0]}}', + }, + }, + }, + ], + }, + }, +} + +export default () => { + return ( +
+ + + +
+ + + 登陆 + + +
+ +
+ + + 登陆 + + +
+
+ +
+
+ ) +} +``` + +#### 纯 JSX 案例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { Field } from '@formily/react' +import { Form, FormItem, Input, Password, Submit } from '@formily/antd' +import { Tabs, Card } from 'antd' +import { UserOutlined, LockOutlined, PhoneOutlined } from '@ant-design/icons' +import { VerifyCode } from './VerifyCode' +import 'antd/lib/tabs/style' +import 'antd/lib/button/style' + +const normalForm = createForm({ + validateFirst: true, +}) + +const phoneForm = createForm({ + validateFirst: true, +}) + +export default () => { + return ( +
+ + + +
+ , + }, + ]} + /> + , + }, + ]} + /> + + 登陆 + + +
+ +
+ , + }, + ]} + /> + { + const phone = field.query('.phone') + field.setComponentProps({ + readyPost: phone.get('valid') && phone.get('value'), + phoneNumber: phone.get('value'), + }) + }} + decorator={[FormItem]} + component={[ + VerifyCode, + { + prefix: , + }, + ]} + /> + + 登陆 + + +
+
+ +
+
+ ) +} +``` + +## 新用户注册 + +#### Markup Schema 案例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { + Form, + FormItem, + FormLayout, + Input, + Select, + Password, + Cascader, + DatePicker, + Submit, + Space, + FormGrid, + Upload, + ArrayItems, + Editable, + FormButtonGroup, +} from '@formily/antd' +import { action } from '@formily/reactive' +import { Card, Button } from 'antd' +import { UploadOutlined } from '@ant-design/icons' +import 'antd/lib/tabs/style' +import 'antd/lib/button/style' + +const form = createForm({ + validateFirst: true, +}) + +const IDUpload = (props) => { + return ( + + + + ) +} + +const SchemaField = createSchemaField({ + components: { + FormItem, + FormGrid, + FormLayout, + Input, + DatePicker, + Cascader, + Select, + Password, + IDUpload, + Space, + ArrayItems, + Editable, + }, + scope: { + fetchAddress: (field) => { + const transform = (data = {}) => { + return Object.entries(data).reduce((buf, [key, value]) => { + if (typeof value === 'string') + return buf.concat({ + label: value, + value: key, + }) + const { name, code, cities, districts } = value + const _cities = transform(cities) + const _districts = transform(districts) + return buf.concat({ + label: name, + value: code, + children: _cities.length + ? _cities + : _districts.length + ? _districts + : undefined, + }) + }, []) + } + + field.loading = true + fetch('//unpkg.com/china-location/dist/location.json') + .then((res) => res.json()) + .then( + action((data) => { + field.dataSource = transform(data) + field.loading = false + }) + ) + }, + }, +}) + +export default () => { + return ( +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 注册 + + +
+
+
+ ) +} +``` + +#### JSON Schema 案例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { + Form, + FormItem, + FormLayout, + Input, + Select, + Password, + Cascader, + DatePicker, + Submit, + Space, + FormGrid, + Upload, + ArrayItems, + Editable, + FormButtonGroup, +} from '@formily/antd' +import { action } from '@formily/reactive' +import { Card, Button } from 'antd' +import { UploadOutlined } from '@ant-design/icons' +import 'antd/lib/tabs/style' +import 'antd/lib/button/style' + +const form = createForm({ + validateFirst: true, +}) + +const IDUpload = (props) => { + return ( + + + + ) +} + +const SchemaField = createSchemaField({ + components: { + FormItem, + FormGrid, + FormLayout, + Input, + DatePicker, + Cascader, + Select, + Password, + IDUpload, + Space, + ArrayItems, + Editable, + }, + scope: { + fetchAddress: (field) => { + const transform = (data = {}) => { + return Object.entries(data).reduce((buf, [key, value]) => { + if (typeof value === 'string') + return buf.concat({ + label: value, + value: key, + }) + const { name, code, cities, districts } = value + const _cities = transform(cities) + const _districts = transform(districts) + return buf.concat({ + label: name, + value: code, + children: _cities.length + ? _cities + : _districts.length + ? _districts + : undefined, + }) + }, []) + } + + field.loading = true + fetch('//unpkg.com/china-location/dist/location.json') + .then((res) => res.json()) + .then( + action((data) => { + field.dataSource = transform(data) + field.loading = false + }) + ) + }, + }, +}) + +const schema = { + type: 'object', + properties: { + username: { + type: 'string', + title: '用户名', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + password: { + type: 'string', + title: '密码', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Password', + 'x-component-props': { + checkStrength: true, + }, + 'x-reactions': [ + { + dependencies: ['.confirm_password'], + fulfill: { + state: { + errors: + '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "确认密码不匹配" : ""}}', + }, + }, + }, + ], + }, + confirm_password: { + type: 'string', + title: '确认密码', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Password', + 'x-component-props': { + checkStrength: true, + }, + 'x-reactions': [ + { + dependencies: ['.password'], + fulfill: { + state: { + errors: + '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "确认密码不匹配" : ""}}', + }, + }, + }, + ], + }, + name: { + type: 'void', + title: '姓名', + 'x-decorator': 'FormItem', + 'x-decorator-props': { + asterisk: true, + feedbackLayout: 'none', + }, + 'x-component': 'FormGrid', + properties: { + firstName: { + type: 'string', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-component-props': { + placeholder: '姓', + }, + }, + lastName: { + type: 'string', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-component-props': { + placeholder: '名', + }, + }, + }, + }, + email: { + type: 'string', + title: '邮箱', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-validator': 'email', + }, + gender: { + type: 'string', + title: '性别', + enum: [ + { + label: '男', + value: 1, + }, + { + label: '女', + value: 2, + }, + { + label: '第三性别', + value: 3, + }, + ], + 'x-decorator': 'FormItem', + 'x-component': 'Select', + }, + birthday: { + type: 'string', + required: true, + title: '生日', + 'x-decorator': 'FormItem', + 'x-component': 'DatePicker', + }, + address: { + type: 'string', + required: true, + title: '地址', + 'x-decorator': 'FormItem', + 'x-component': 'Cascader', + 'x-reactions': '{{fetchAddress}}', + }, + idCard: { + type: 'string', + required: true, + title: '身份证复印件', + 'x-decorator': 'FormItem', + 'x-component': 'IDUpload', + }, + contacts: { + type: 'array', + required: true, + title: '联系人信息', + 'x-decorator': 'FormItem', + 'x-component': 'ArrayItems', + items: { + type: 'object', + 'x-component': 'ArrayItems.Item', + properties: { + sort: { + type: 'void', + 'x-decorator': 'FormItem', + 'x-component': 'ArrayItems.SortHandle', + }, + popover: { + type: 'void', + title: '完善联系人信息', + 'x-decorator': 'Editable.Popover', + 'x-component': 'FormLayout', + 'x-component-props': { + layout: 'vertical', + }, + 'x-reactions': [ + { + dependencies: ['.popover.name'], + fulfill: { + schema: { + title: '{{$deps[0]}}', + }, + }, + }, + ], + properties: { + name: { + type: 'string', + title: '姓名', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-component-props': { + style: { + width: 300, + }, + }, + }, + email: { + type: 'string', + title: '邮箱', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-validator': [{ required: true }, 'email'], + 'x-component-props': { + style: { + width: 300, + }, + }, + }, + phone: { + type: 'string', + title: '手机号', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-validator': [{ required: true }, 'phone'], + 'x-component-props': { + style: { + width: 300, + }, + }, + }, + }, + }, + remove: { + type: 'void', + 'x-decorator': 'FormItem', + 'x-component': 'ArrayItems.Remove', + }, + }, + }, + properties: { + addition: { + type: 'void', + title: '新增联系人', + 'x-component': 'ArrayItems.Addition', + }, + }, + }, + }, +} + +export default () => { + return ( +
+ +
+ + + + 注册 + + + +
+
+ ) +} +``` + +#### 纯 JSX 案例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { Field, VoidField } from '@formily/react' +import { + Form, + FormItem, + Input, + Select, + Password, + Cascader, + DatePicker, + Submit, + FormGrid, + Upload, + FormButtonGroup, +} from '@formily/antd' +import { action } from '@formily/reactive' +import { Card, Button } from 'antd' +import { UploadOutlined } from '@ant-design/icons' +import 'antd/lib/tabs/style' +import 'antd/lib/button/style' + +const form = createForm({ + validateFirst: true, +}) + +const IDUpload = (props) => { + return ( + + + + ) +} + +export default () => { + return ( +
+ +
+ + { + const confirm = field.query('.confirm_password') + field.errors = + confirm.get('value') && + field.value && + field.value !== confirm.get('value') + ? '确认密码不匹配' + : '' + }} + /> + { + const password = field.query('.password') + field.errors = + password.get('value') && + field.value && + field.value !== password.get('value') + ? '确认密码不匹配' + : '' + }} + /> + + + + + + + + { + const transform = (data = {}) => { + return Object.entries(data).reduce((buf, [key, value]) => { + if (typeof value === 'string') + return buf.concat({ + label: value, + value: key, + }) + const { name, code, cities, districts } = value + const _cities = transform(cities) + const _districts = transform(districts) + return buf.concat({ + label: name, + value: code, + children: _cities.length + ? _cities + : _districts.length + ? _districts + : undefined, + }) + }, []) + } + + field.loading = true + fetch('//unpkg.com/china-location/dist/location.json') + .then((res) => res.json()) + .then( + action((data) => { + field.dataSource = transform(data) + field.loading = false + }) + ) + }} + /> + + + + 注册 + + + +
+
+ ) +} +``` + +## 忘记密码 + +#### Markup Schema 案例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { + Form, + FormItem, + Input, + Password, + Submit, + FormButtonGroup, +} from '@formily/antd' +import { Card } from 'antd' +import 'antd/lib/button/style' + +const form = createForm({ + validateFirst: true, +}) + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + Password, + }, +}) + +export default () => { + return ( +
+ +
+ + + + + + + + + + 确认变更 + + +
+
+
+ ) +} +``` + +#### JSON Schema 案例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { createSchemaField } from '@formily/react' +import { + Form, + FormItem, + Input, + Password, + Submit, + FormButtonGroup, +} from '@formily/antd' +import { Card } from 'antd' +import 'antd/lib/button/style' + +const form = createForm({ + validateFirst: true, +}) + +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + Password, + }, +}) + +const schema = { + type: 'object', + properties: { + username: { + type: 'string', + title: '用户名', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + email: { + type: 'string', + title: '邮箱', + required: true, + 'x-validator': 'email', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + oldPassword: { + type: 'string', + title: '原始密码', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Password', + }, + password: { + type: 'string', + title: '新密码', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Password', + 'x-component-props': { + checkStrength: true, + }, + 'x-reactions': [ + { + dependencies: ['.confirm_password'], + fulfill: { + state: { + errors: + '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "确认密码不匹配" : ""}}', + }, + }, + }, + ], + }, + confirm_password: { + type: 'string', + title: '确认密码', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Password', + 'x-component-props': { + checkStrength: true, + }, + 'x-reactions': [ + { + dependencies: ['.password'], + fulfill: { + state: { + errors: + '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "确认密码不匹配" : ""}}', + }, + }, + }, + ], + }, + }, +} + +export default () => { + return ( +
+ +
+ + + + 确认变更 + + + +
+
+ ) +} +``` + +#### 纯 JSX 案例 + +```tsx +import React from 'react' +import { createForm } from '@formily/core' +import { Field } from '@formily/react' +import { + Form, + FormItem, + Input, + Password, + Submit, + FormButtonGroup, +} from '@formily/antd' +import { Card } from 'antd' +import 'antd/lib/button/style' + +const form = createForm({ + validateFirst: true, +}) + +export default () => { + return ( +
+ +
+ + + + { + const confirm = field.query('.confirm_password') + field.errors = + confirm.get('value') && + field.value && + field.value !== confirm.get('value') + ? '确认密码不匹配' + : '' + }} + /> + { + const confirm = field.query('.password') + field.errors = + confirm.get('value') && + field.value && + field.value !== confirm.get('value') + ? '确认密码不匹配' + : '' + }} + /> + + + 确认变更 + + + +
+
+ ) +} +``` diff --git a/docs/guide/scenes/more.md b/docs/guide/scenes/more.md index 6161bbbfcff..140b6b2d1ae 100644 --- a/docs/guide/scenes/more.md +++ b/docs/guide/scenes/more.md @@ -1,5 +1,6 @@ -# 更多场景 +# More Scenes -因为Formily在表单层面上是一个非常完备的方案,而且还很灵活,支持的场景非常多,但是场景案例,我们无法一一列举。 +Because Formily is a very complete solution at the form level, and it is also very flexible. It supports a lot of scenarios, but we can't list them all. + +Therefore, I still hope that the community can help Formily improve more scenarios! We would be very grateful!😀 -所以,还是希望社区能帮助Formily完善更多场景案例!我们会不胜感激!😀 \ No newline at end of file diff --git a/docs/guide/scenes/more.zh-CN.md b/docs/guide/scenes/more.zh-CN.md new file mode 100644 index 00000000000..6161bbbfcff --- /dev/null +++ b/docs/guide/scenes/more.zh-CN.md @@ -0,0 +1,5 @@ +# 更多场景 + +因为Formily在表单层面上是一个非常完备的方案,而且还很灵活,支持的场景非常多,但是场景案例,我们无法一一列举。 + +所以,还是希望社区能帮助Formily完善更多场景案例!我们会不胜感激!😀 \ No newline at end of file diff --git a/docs/guide/scenes/query-list.md b/docs/guide/scenes/query-list.md index f6254b9bc38..326d7ca9cda 100644 --- a/docs/guide/scenes/query-list.md +++ b/docs/guide/scenes/query-list.md @@ -1,9 +1,11 @@ -# 查询列表 +# Query List -查询列表目前formily对应的解决方案是阿里巴巴统一列表组件 [AList](https://alist.wiki),具体参考AList写法 +The current query list solution corresponding to formily is [AList](https://alist.wiki), the unified list component of Alibaba. +Specific reference to AList writing. -在1.x中,我们提供了useFormTableQuery的React Hook给用户使用,但是发现,该Hook -- 在简单场景,其实使用 [ahooks](https://ahooks.js.org/)中的useTable就能解决问题 -- 复杂场景,useFormTableQuery的抽象度又并不高,还是会导致很多样板代码,相反 [AList](https://alist.wiki) 在复杂场景的表现则很优秀 +In 1.x, we provided the React Hook of useFormTableQuery for users to use, but found that the Hook +- In simple scenarios, the useTable in [ahooks](https://ahooks.js.org/) can actually solve the problem +- In complex scenarios, the abstraction of useFormTableQuery is not high, and it will still lead to a lot of boilerplate code. On the contrary, [AList](https://alist.wiki) performs well in complex scenarios. + +So, let the professional project solve the professional field. Formily's positioning is always the form. -所以,专业的领域就让专业的项目去解决吧,Formily的定位始终还是表单 \ No newline at end of file diff --git a/docs/guide/scenes/query-list.zh-CN.md b/docs/guide/scenes/query-list.zh-CN.md new file mode 100644 index 00000000000..f6254b9bc38 --- /dev/null +++ b/docs/guide/scenes/query-list.zh-CN.md @@ -0,0 +1,9 @@ +# 查询列表 + +查询列表目前formily对应的解决方案是阿里巴巴统一列表组件 [AList](https://alist.wiki),具体参考AList写法 + +在1.x中,我们提供了useFormTableQuery的React Hook给用户使用,但是发现,该Hook +- 在简单场景,其实使用 [ahooks](https://ahooks.js.org/)中的useTable就能解决问题 +- 复杂场景,useFormTableQuery的抽象度又并不高,还是会导致很多样板代码,相反 [AList](https://alist.wiki) 在复杂场景的表现则很优秀 + +所以,专业的领域就让专业的项目去解决吧,Formily的定位始终还是表单 \ No newline at end of file diff --git a/docs/guide/scenes/step-form.md b/docs/guide/scenes/step-form.md index 3c87a186a9a..49ec8239555 100644 --- a/docs/guide/scenes/step-form.md +++ b/docs/guide/scenes/step-form.md @@ -1,3 +1,4 @@ -# 分步表单 +# Step-by-Step Form + +Mainly use the [FormStep](https://antd.formilyjs.org/components/form-step) component in [@formily/antd](https://antd.formilyjs.org) or [@formily/next](ttps://next.formilyjs.org) -主要使用[@formily/antd](https://antd.formilyjs.org) 或 [@formily/next](https://next.formilyjs.org) 中的[FormStep](https://antd.formilyjs.org/components/form-step)组件 \ No newline at end of file diff --git a/docs/guide/scenes/step-form.zh-CN.md b/docs/guide/scenes/step-form.zh-CN.md new file mode 100644 index 00000000000..3c87a186a9a --- /dev/null +++ b/docs/guide/scenes/step-form.zh-CN.md @@ -0,0 +1,3 @@ +# 分步表单 + +主要使用[@formily/antd](https://antd.formilyjs.org) 或 [@formily/next](https://next.formilyjs.org) 中的[FormStep](https://antd.formilyjs.org/components/form-step)组件 \ No newline at end of file diff --git a/docs/guide/scenes/tab-form.md b/docs/guide/scenes/tab-form.md index 4b43442fd59..a39b9b6039a 100644 --- a/docs/guide/scenes/tab-form.md +++ b/docs/guide/scenes/tab-form.md @@ -1,3 +1,4 @@ -# 选项卡/手风琴表单 +# Tab/Accordion Form + +Mainly use the [FormTab](https://antd.formilyjs.org/components/form-tab) component and [FormCollapse](https://antd.formilyjs.org/components/form-collapse) component in [@formily/antd](https://antd.formilyjs.org) or [@formily/next](https://next.formilyjs.org) -主要使用[@formily/antd](https://antd.formilyjs.org) 或 [@formily/next](https://next.formilyjs.org) 中的[FormTab](https://antd.formilyjs.org/components/form-tab)组件 与 [FormCollapse](https://antd.formilyjs.org/components/form-collapse)组件 diff --git a/docs/guide/scenes/tab-form.zh-CN.md b/docs/guide/scenes/tab-form.zh-CN.md new file mode 100644 index 00000000000..4b43442fd59 --- /dev/null +++ b/docs/guide/scenes/tab-form.zh-CN.md @@ -0,0 +1,3 @@ +# 选项卡/手风琴表单 + +主要使用[@formily/antd](https://antd.formilyjs.org) 或 [@formily/next](https://next.formilyjs.org) 中的[FormTab](https://antd.formilyjs.org/components/form-tab)组件 与 [FormCollapse](https://antd.formilyjs.org/components/form-collapse)组件 diff --git a/docs/guide/upgrade.md b/docs/guide/upgrade.md index 416f30ca0c3..41fbbb863e8 100644 --- a/docs/guide/upgrade.md +++ b/docs/guide/upgrade.md @@ -1,238 +1,233 @@ -# V2 升级指南 +# V2 Upgrade Guide -这里着重提一下,Formily2 相比于 Formily1.x,差别非常大,存在大量 Break Change。 +It is important to mention here that Formily2 is very different from Formily1.x, and there are a lot of Break Changes. -所以对老用户而言,基本上是需要重新学习的,V1 和 V2 是无法做到平滑升级的。 +Therefore, for old users, they basically need to learn again, and V1 and V2 cannot be upgraded smoothly. -但是 Formily2 的项目初衷就是为了降低大家的学习成本,因为老用户本身已经对 Formily 的核心思想有过一定的了解,为了帮助老用户更快速的学习 Formily2,本文会列举出 V1 和 V2 的核心差异点,并不会列举新增的能力。 +But the original intention of the Formily2 project is to reduce everyone's learning costs, because the old users themselves have a certain understanding of Formily's core ideas. In order to help old users learn Formily2 more quickly, this article will list the core differences between V1 and V2. , and will not list the new capabilities. -## 内核差异 +## Kernel Difference -> 这里主要指@formily/core 的差异 +> This mainly refers to the difference between @formily/core -因为 Formily1.x 用户在使用内核 API 的时候,主要是使用 setFieldState/setFormState 与 getFieldState/getFormState,在 V2 中保留了这些 API,但是内部的模型属性是有语义上的差别的,差别如下: +Because Formily1.x users mainly use setFieldState/setFormState and getFieldState/getFormState when using the core APIs, these APIs are retained in V2, but the internal model properties are semantically different. The differences are as follows: **modified** -- V1: 代表字段是否已改动,其实并没有任何用处,因为字段初始化就代表已改动 -- V2: 代表字段是否被手动修改,也就是组件触发 onChange 事件的时候才会设置为 true +- V1: Represent whether the field has been changed, in fact, it is of no use, because the initialization of the field means that it has been changed. +- V2: Indicates whether the field is manually modified, that is, it will be set to true when the component triggers the onChange event. **inputed** -- V1: 代表字段是否被手动修改 -- V2: 移除,统一使用 modified +- V1: Represent Whether the field has been manually modified +- V2: Remove, use modified uniformly **pristine** -- V1: 代表字段 value 是否等于 initialValue -- V2: 移除,用户手动判断,该属性会导致大量脏检查 +- V1:Represent whether the field value is equal to initialValue +- V2: Remove, user manual judgment, this attribute will cause a lot of dirty checks **display** -- V1: 代表字段是否显示,如果为 false,不会移除字段值 -- V2: 代表字段展示模式,值为`"none" | "visible" | "hidden"` +- V1: Represent whether the field is displayed, if it is false, the field value will not be removed +- V2: Represent the field display mode, the value is `"none" | "visible" | "hidden"` **touched** -- V1: 冗余字段 -- V2: 移除 +- V1: Redundant field +- V2: Remove **validating** -- V1: 代表字段是否正在校验 -- V2: 移除,统一使用 validateStatus +- V1: Whether the representative field is being verified +- V2: Remove, use validateStatus uniformly **effectErrors/effectWarnings** -- V1: 代表用户手动操作的 errors 和 warnings -- V2: 移除,统一使用 feedbacks +- V1: Errors and warnings that represent the manual operation of the user +- V2: Remove, use feedbacks uniformly **ruleErrors/ruleWarnings** -- V1: 代表校验器校验操作的 errors 与 warnings -- V2: 移除,统一使用 feedbacks +- V1: Errors and warnings representing the verification operation of the validator +- V2: Remove, use feedbacks uniformly **values** -- V1: 代表 onChange 事件返回的所有参数 -- V2: 移除,统一使用 inputValues +- V1: Represent all the parameters returned by the onChange event +- V2: Remove, use inputValues uniformly **rules** -- V1:代表校验规则 -- V2:移除,统一使用 validator,因为 rules 的字面意思是规则,但是规则的含义很大,不局限于校验规则 +- V1: Represent verification rules +- V2: Remove, use validator uniformly, because rules literally means rules, but the meaning of rules is very big, not limited to verification rules **props** -- V1:代表组件的扩展属性,定位很不清晰,在纯 JSX 场景是代表组件属性与 FormItem 属性的集合,在 Schema 场景又是代表 Schema 字段的属性 -- V2: 移除,统一使用 decorator 和 component +- V1: Represent the extended attributes of the component, and the positioning is very unclear. In the pure JSX scenario, it represents the collection of component attributes and FormItem attributes. In the Schema scenario, it represents the attributes of the Schema field. +- V2: Remove, use decorator and component uniformly **VirtualField** -- V1: 代表虚拟字段 -- V2: 改名,统一使用[VoidField](https://core.formilyjs.org/api/models/void-field) +- V1: Represents a virtual field +- V2: Renamed and use [VoidField](https://core.formilyjs.org/api/models/void-field) uniformly -**unmount行为** +## Bridge layer differences -- V1: 字段unmount,字段值默认会被删除 -- V2: 移除,这个默认行为太隐晦,如果要删值,可以直接修改value,同时自动删值的行为只有字段display为none时才会自动删值 - -## 桥接层差异 - -> 这里主要指@formily/react 和@formily/react-schema-renderer 的差异 +> This mainly refers to the difference between @formily/react and @formily/react-schema-renderer. **createFormActions/createAsyncFormActions** -- V1 创建一个 Form 操作器,可以调用 setFieldState/setFormState 方法 -- V2 移除,统一使用@formily/core 中的[createForm](https://core.formilyjs.org/api/entry/create-form)创建出来的 Form 实例操作状态 +- V1 Create a Form operator, you can call the setFieldState/setFormState method. +- V2 is removed, and the operation status of the Form instance created by [createForm](https://core.formilyjs.org/api/entry/create-form) in @formily/core is used uniformly. **Form** -- V1 内部会创建 Form 实例,可以受控传递 values/initialValues 属性等 -- V2 移除,统一使用[FormProvider](https://react.formilyjs.org/api/components/form-provider) +- V1 will create a Form instance inside, which can control the transfer of values/initialValues attributes, etc. +- V2 removed, unified use of [FormProvider](https://react.formilyjs.org/api/components/form-provider) **SchemaForm** -- V1 内部会解析 json-schema 协议,同时会创建 Form 实例,支持受控模式,并渲染 -- V2 移除,统一使用[createSchemaField](https://react.formilyjs.org/api/components/schema-field)创建出来的 SchemaField 组件,且不支持受控模式 +- V1 will parse the json-schema protocol internally, create a Form instance, support controlled mode, and render it. +- V2 is removed, the SchemaField component created by [createSchemaField](https://react.formilyjs.org/api/components/schema-field) is used uniformly, and the controlled mode is not supported. **Field** -- V1 支持受控模式,需要使用 render props 进行组件状态映射 -- V2 不支持受控模式,传入 decorator/component 属性即可快速实现状态映射 +- V1 supports controlled mode, which requires the use of render props for component state mapping. +- V2 does not support controlled mode, you can quickly implement state mapping by passing in the decorator/component property. **VirtualField** -- V1 支持受控模式,需要使用 render props 进行组件状态映射 -- V2 不支持受控模式,改名[VoidField](https://react.formilyjs.org/api/components/void-field),传入 decorator/component 属性即可快速实现状态映射 +- V1 supports controlled mode, which requires the use of render props for component state mapping. +- V2 does not support controlled mode, renamed [VoidField](https://react.formilyjs.org/api/components/void-field), and passed in the decorator/component property to quickly implement state mapping. **FieldList** -- V1 代表自增字段控制组件 -- V2 改名为[ArrayField](https://react.formilyjs.org/api/components/array-field) +- V1 Represent auto-incremented field control component +- V2 Renamed to [ArrayField](https://react.formilyjs.org/api/components/array-field) **FormSpy** -- V1 监听所有生命周期触发,并重新渲染 -- V2 移除,统一使用[FormConsumer](https://react.formilyjs.org/api/components/form-consumer) +- V1 Monitor all life cycle triggers and re-render +- V2 Remove and use [FormConsumer](https://react.formilyjs.org/api/components/form-consumer) **SchemaMarkupField** -- V1 代表 Schema 描述标签组件 -- V2 移除,统一使用[createSchemaField](https://react.formilyjs.org/api/components/schema-field)工厂函数创建出来的描述标签组件 +- V1 Stands for Schema description label component +- V2 Remove, unified use the description label component created by the [createSchemaField](https://react.formilyjs.org/api/components/schema-field) **useFormQuery** -- V1 用于实现表单查询的快捷 Hook,支持中间件机制 -- V2 暂时移除 +- V1 Fast Hook for realizing form query, supporting middleware mechanism +- V2 Temporarily remove **useForm** -- V1 代表创建 Form 实例 -- V2 代表消费上下文中的 Form 实例,如果要创建,请使用[createForm](https://react.formilyjs.org/api/entry/create-form) +- V1 Represents the creation of a Form instance +- V2 Represents the Form instance in the consumption context, if you want to create it, please use [createForm](https://react.formilyjs.org/api/entry/create-form) **useField** -- V1 代表创建 Field 实例 -- V2 代表消费上下文中的 Field 实例,如果要创建,请调用[form.createField](https://core.formilyjs.org/api/models/form#createfield) +- V1 Represents the creation of a Field instance +- V2 Represents the Field instance in the consumption context, if you want to create it, please call [form.createField](https://core.formilyjs.org/api/models/form#createfield) **useVirtualField** -- V1 代表创建 VirtualField 实例 -- V2 移除,如果要创建,请调用[form.createVoidField](https://core.formilyjs.org/api/models/form#createvoidfield) +- V1 Represents the creation of a VirtualField instance +- V2 Remove, if you want to create, please call [form.createVoidField](https://core.formilyjs.org/api/models/form#createvoidfield) **useFormState** -- V1 消费上下文中的 Form 状态 -- V2 移除,统一使用[useForm](https://react.formilyjs.org/api/hooks/use-form) +- V1 Form state in consumption context +- V2 Remove, use [useForm](https://react.formilyjs.org/api/hooks/use-form) uniformly **useFieldState** -- V1 消费上下文中的 Field 状态 -- V2 移除,统一使用[useField](https://react.formilyjs.org/api/hooks/use-field) +- V1 consume Field status in context +- V2 Remove, use [useField](https://react.formilyjs.org/api/hooks/use-field) **useFormSpy** -- V1 创建生命周期监听器,并触发重新渲染 -- V2 移除 +- V1 Create a lifecycle listener and trigger a re-render +- V2 Remove **useSchemaProps** -- V1 消费上下文中的 SchemaField 的 Props -- V2 移除,统一使用[useFieldSchema](https://react.formilyjs.org/api/hooks/use-field-schema) +- V1Cconsume rops of SchemaField in context +- V2 Remove, use [useFieldSchema](https://react.formilyjs.org/api/hooks/use-field-schema) uniformly **connect** -- V1 标准 HOC -- V2 高阶函数改为 1 阶,属性有巨大变化,具体看[connect 文档](https://react.formilyjs.org/api/shared/connect) +- V1 Standard HOC +- V2 The higher-order function is changed to 1st order, and the properties have changed dramatically. See the [connect document](https://react.formilyjs.org/api/shared/connect) for details **registerFormField/registerVirtaulBox/registerFormComponent/registerFormItemComponent** -- V1 全局注册组件 -- V2 移除,不再支持全局注册 +- V1 Globally registered components +- V2 Remove, global registration is no longer supported **FormEffectHooks** -- V1 RxJS 生命周期钩子 -- V2 移除,统一从@formily/core 中导出,且不会返回 RxJS Observable 对象 +- V1 RxJS lifecycle hook +- V2 Remove, export from @formily/core uniformly, and will not return RxJS Observable object **effects** -- V1 支持回调函数`$`选择器 -- V2 移除`$`选择器 +- V1 Support callback function`$` selector +- V2 Remove`$`selector -## 协议层差异 +## Protocol layer differences -> 这里主要指 JSON Schema 协议上的差异 +> This mainly refers to the difference in the JSON Schema protocol **editable** -- V1 直接在 Schema 描述中,代表字段是否可编辑 -- V2 改名 x-editable +- V1 is directly in the Schema description, indicating whether the field can be edited +- V2 Renamed x-editable **visible** -- V1 代表字段是否显示 -- V2 改名 x-visible +- V1 Indicates whether the field is displayed +- V2 Renamed x-visible **display** -- V1 代表字段是否显示,如果为 false,代表不删值的隐藏行为 -- V2 改名 x-display,代表字段展示模式,值为`"none" | "visible" | "hidden"` +- V1 Represent whether the field is displayed or not, if it is false, it represents the hidden behavior without deleting the value +- V2 Renamed x-display, which represents the field display mode, and the value is`"none" | "visible" | "hidden"` **triggerType** -- V1 代表字段校验时机 -- V2 移除,请使用`x-validator:[{triggerType:"onBlur",validator:()=>...}]` +- V1 Represent the field verification timing +- V2 Remove, please use`x-validator:[{triggerType:"onBlur",validator:()=>...}]` **x-props** -- V1 代表 FormItem 属性 -- V2 移除,请使用 x-decorator-props +- V1 Represents the FormItem property +- V2 Remove, please use x-decorator-props **x-rules** -- V1 代表字段校验规则 -- V2 改名 x-validator +- V1 Represent field verification rules +- V2 Renamed x-validator **x-linkages** -- V1 代表字段联动 -- V2 移除,统一使用 x-reactions +- V1 Represent field linkage +- V2 Remove, use x-reactions uniformly **x-mega-props** -- V1 代表 MegaLayout 组件的子组件属性 -- V2 移除 +- V1 Represent the sub-component properties of the MegaLayout component +- V2 Remove -## 组件库差异 +## Component library differences -在 Formily1.x 中,我们主要使用@formily/antd 和@formily/antd-components,或者@formily/next 和@formily/next-components, +In Formily 1.x, we mainly use @formily/antd and @formily/antd-components, or @formily/next and @formily/next-components. -在 V2 中,我们有以下几个改变: +In V2, we have the following changes: -- @formily/antd 与@formily/antd-components 合并成@formily/antd,同时目录结构全部改成纯组件库的目录结构了。 +- @formily/antd and @formily/antd-components were merged into @formily/antd, and the directory structure was changed to that of a pure component library. -- 不会再导出@formily/react @formily/core 的内部 API -- 所有组件几乎都做了重写,无法平滑升级 -- 移除 styled-components +- The internal API of @formily/react @formily/core will no longer be exported. +- Almost all components have been rewritten and cannot be smoothly upgraded. +- Remove styled-components. diff --git a/docs/guide/upgrade.zh-CN.md b/docs/guide/upgrade.zh-CN.md new file mode 100644 index 00000000000..416f30ca0c3 --- /dev/null +++ b/docs/guide/upgrade.zh-CN.md @@ -0,0 +1,238 @@ +# V2 升级指南 + +这里着重提一下,Formily2 相比于 Formily1.x,差别非常大,存在大量 Break Change。 + +所以对老用户而言,基本上是需要重新学习的,V1 和 V2 是无法做到平滑升级的。 + +但是 Formily2 的项目初衷就是为了降低大家的学习成本,因为老用户本身已经对 Formily 的核心思想有过一定的了解,为了帮助老用户更快速的学习 Formily2,本文会列举出 V1 和 V2 的核心差异点,并不会列举新增的能力。 + +## 内核差异 + +> 这里主要指@formily/core 的差异 + +因为 Formily1.x 用户在使用内核 API 的时候,主要是使用 setFieldState/setFormState 与 getFieldState/getFormState,在 V2 中保留了这些 API,但是内部的模型属性是有语义上的差别的,差别如下: + +**modified** + +- V1: 代表字段是否已改动,其实并没有任何用处,因为字段初始化就代表已改动 +- V2: 代表字段是否被手动修改,也就是组件触发 onChange 事件的时候才会设置为 true + +**inputed** + +- V1: 代表字段是否被手动修改 +- V2: 移除,统一使用 modified + +**pristine** + +- V1: 代表字段 value 是否等于 initialValue +- V2: 移除,用户手动判断,该属性会导致大量脏检查 + +**display** + +- V1: 代表字段是否显示,如果为 false,不会移除字段值 +- V2: 代表字段展示模式,值为`"none" | "visible" | "hidden"` + +**touched** + +- V1: 冗余字段 +- V2: 移除 + +**validating** + +- V1: 代表字段是否正在校验 +- V2: 移除,统一使用 validateStatus + +**effectErrors/effectWarnings** + +- V1: 代表用户手动操作的 errors 和 warnings +- V2: 移除,统一使用 feedbacks + +**ruleErrors/ruleWarnings** + +- V1: 代表校验器校验操作的 errors 与 warnings +- V2: 移除,统一使用 feedbacks + +**values** + +- V1: 代表 onChange 事件返回的所有参数 +- V2: 移除,统一使用 inputValues + +**rules** + +- V1:代表校验规则 +- V2:移除,统一使用 validator,因为 rules 的字面意思是规则,但是规则的含义很大,不局限于校验规则 + +**props** + +- V1:代表组件的扩展属性,定位很不清晰,在纯 JSX 场景是代表组件属性与 FormItem 属性的集合,在 Schema 场景又是代表 Schema 字段的属性 +- V2: 移除,统一使用 decorator 和 component + +**VirtualField** + +- V1: 代表虚拟字段 +- V2: 改名,统一使用[VoidField](https://core.formilyjs.org/api/models/void-field) + +**unmount行为** + +- V1: 字段unmount,字段值默认会被删除 +- V2: 移除,这个默认行为太隐晦,如果要删值,可以直接修改value,同时自动删值的行为只有字段display为none时才会自动删值 + +## 桥接层差异 + +> 这里主要指@formily/react 和@formily/react-schema-renderer 的差异 + +**createFormActions/createAsyncFormActions** + +- V1 创建一个 Form 操作器,可以调用 setFieldState/setFormState 方法 +- V2 移除,统一使用@formily/core 中的[createForm](https://core.formilyjs.org/api/entry/create-form)创建出来的 Form 实例操作状态 + +**Form** + +- V1 内部会创建 Form 实例,可以受控传递 values/initialValues 属性等 +- V2 移除,统一使用[FormProvider](https://react.formilyjs.org/api/components/form-provider) + +**SchemaForm** + +- V1 内部会解析 json-schema 协议,同时会创建 Form 实例,支持受控模式,并渲染 +- V2 移除,统一使用[createSchemaField](https://react.formilyjs.org/api/components/schema-field)创建出来的 SchemaField 组件,且不支持受控模式 + +**Field** + +- V1 支持受控模式,需要使用 render props 进行组件状态映射 +- V2 不支持受控模式,传入 decorator/component 属性即可快速实现状态映射 + +**VirtualField** + +- V1 支持受控模式,需要使用 render props 进行组件状态映射 +- V2 不支持受控模式,改名[VoidField](https://react.formilyjs.org/api/components/void-field),传入 decorator/component 属性即可快速实现状态映射 + +**FieldList** + +- V1 代表自增字段控制组件 +- V2 改名为[ArrayField](https://react.formilyjs.org/api/components/array-field) + +**FormSpy** + +- V1 监听所有生命周期触发,并重新渲染 +- V2 移除,统一使用[FormConsumer](https://react.formilyjs.org/api/components/form-consumer) + +**SchemaMarkupField** + +- V1 代表 Schema 描述标签组件 +- V2 移除,统一使用[createSchemaField](https://react.formilyjs.org/api/components/schema-field)工厂函数创建出来的描述标签组件 + +**useFormQuery** + +- V1 用于实现表单查询的快捷 Hook,支持中间件机制 +- V2 暂时移除 + +**useForm** + +- V1 代表创建 Form 实例 +- V2 代表消费上下文中的 Form 实例,如果要创建,请使用[createForm](https://react.formilyjs.org/api/entry/create-form) + +**useField** + +- V1 代表创建 Field 实例 +- V2 代表消费上下文中的 Field 实例,如果要创建,请调用[form.createField](https://core.formilyjs.org/api/models/form#createfield) + +**useVirtualField** + +- V1 代表创建 VirtualField 实例 +- V2 移除,如果要创建,请调用[form.createVoidField](https://core.formilyjs.org/api/models/form#createvoidfield) + +**useFormState** + +- V1 消费上下文中的 Form 状态 +- V2 移除,统一使用[useForm](https://react.formilyjs.org/api/hooks/use-form) + +**useFieldState** + +- V1 消费上下文中的 Field 状态 +- V2 移除,统一使用[useField](https://react.formilyjs.org/api/hooks/use-field) + +**useFormSpy** + +- V1 创建生命周期监听器,并触发重新渲染 +- V2 移除 + +**useSchemaProps** + +- V1 消费上下文中的 SchemaField 的 Props +- V2 移除,统一使用[useFieldSchema](https://react.formilyjs.org/api/hooks/use-field-schema) + +**connect** + +- V1 标准 HOC +- V2 高阶函数改为 1 阶,属性有巨大变化,具体看[connect 文档](https://react.formilyjs.org/api/shared/connect) + +**registerFormField/registerVirtaulBox/registerFormComponent/registerFormItemComponent** + +- V1 全局注册组件 +- V2 移除,不再支持全局注册 + +**FormEffectHooks** + +- V1 RxJS 生命周期钩子 +- V2 移除,统一从@formily/core 中导出,且不会返回 RxJS Observable 对象 + +**effects** + +- V1 支持回调函数`$`选择器 +- V2 移除`$`选择器 + +## 协议层差异 + +> 这里主要指 JSON Schema 协议上的差异 + +**editable** + +- V1 直接在 Schema 描述中,代表字段是否可编辑 +- V2 改名 x-editable + +**visible** + +- V1 代表字段是否显示 +- V2 改名 x-visible + +**display** + +- V1 代表字段是否显示,如果为 false,代表不删值的隐藏行为 +- V2 改名 x-display,代表字段展示模式,值为`"none" | "visible" | "hidden"` + +**triggerType** + +- V1 代表字段校验时机 +- V2 移除,请使用`x-validator:[{triggerType:"onBlur",validator:()=>...}]` + +**x-props** + +- V1 代表 FormItem 属性 +- V2 移除,请使用 x-decorator-props + +**x-rules** + +- V1 代表字段校验规则 +- V2 改名 x-validator + +**x-linkages** + +- V1 代表字段联动 +- V2 移除,统一使用 x-reactions + +**x-mega-props** + +- V1 代表 MegaLayout 组件的子组件属性 +- V2 移除 + +## 组件库差异 + +在 Formily1.x 中,我们主要使用@formily/antd 和@formily/antd-components,或者@formily/next 和@formily/next-components, + +在 V2 中,我们有以下几个改变: + +- @formily/antd 与@formily/antd-components 合并成@formily/antd,同时目录结构全部改成纯组件库的目录结构了。 + +- 不会再导出@formily/react @formily/core 的内部 API +- 所有组件几乎都做了重写,无法平滑升级 +- 移除 styled-components diff --git a/docs/index.md b/docs/index.md index 12c2ab037f0..fbff5513ae3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,26 +1,26 @@ --- -title: Formily - 阿里巴巴统一前端表单解决方案 +title: Formily - Alibaba unified front-end form solution order: 10 hero: title: Alibaba Formily - desc: 阿里巴巴统一前端表单解决方案 + desc: Alibaba unified front-end form solution actions: - - text: 快速开始 + - text: Quick start link: /guide features: - icon: https://img.alicdn.com/imgextra/i2/O1CN016i72sH1c5wh1kyy9U_!!6000000003550-55-tps-800-800.svg - title: 更易用 - desc: 开箱即用,案例丰富 + title: Easier to use + desc: Out of the box, rich cases - icon: https://img.alicdn.com/imgextra/i1/O1CN01bHdrZJ1rEOESvXEi5_!!6000000005599-55-tps-800-800.svg - title: 更高效 - desc: 傻瓜写法,超高性能 + title: More efficient + desc: Fool writing, ultra-high performance - icon: https://img.alicdn.com/imgextra/i3/O1CN01xlETZk1G0WSQT6Xii_!!6000000000560-55-tps-800-800.svg - title: 更专业 - desc: 完备,灵活,优雅 + title: More Professional + desc: Complete, flexible and elegant footer: Open-source MIT Licensed | Copyright © 2019-present
Powered by self --- -## 安装依赖 +## Install dependencies ```bash @@ -36,7 +36,7 @@ $ npm install --save @alifd/next moment ``` -## 安装 Formily +## Install Formily ```bash $ npm install --save @formily/core @formily/react @formily/antd diff --git a/docs/index.zh-CN.md b/docs/index.zh-CN.md new file mode 100644 index 00000000000..12c2ab037f0 --- /dev/null +++ b/docs/index.zh-CN.md @@ -0,0 +1,51 @@ +--- +title: Formily - 阿里巴巴统一前端表单解决方案 +order: 10 +hero: + title: Alibaba Formily + desc: 阿里巴巴统一前端表单解决方案 + actions: + - text: 快速开始 + link: /guide +features: + - icon: https://img.alicdn.com/imgextra/i2/O1CN016i72sH1c5wh1kyy9U_!!6000000003550-55-tps-800-800.svg + title: 更易用 + desc: 开箱即用,案例丰富 + - icon: https://img.alicdn.com/imgextra/i1/O1CN01bHdrZJ1rEOESvXEi5_!!6000000005599-55-tps-800-800.svg + title: 更高效 + desc: 傻瓜写法,超高性能 + - icon: https://img.alicdn.com/imgextra/i3/O1CN01xlETZk1G0WSQT6Xii_!!6000000000560-55-tps-800-800.svg + title: 更专业 + desc: 完备,灵活,优雅 +footer: Open-source MIT Licensed | Copyright © 2019-present
Powered by self +--- + +## 安装依赖 + +```bash + +$ npm install --save antd moment + +``` + +or + +```bash + +$ npm install --save @alifd/next moment + +``` + +## 安装 Formily + +```bash +$ npm install --save @formily/core @formily/react @formily/antd + +``` + +or + +```bash +$ npm install --save @formily/core @formily/react @formily/next + +``` diff --git a/packages/antd/src/array-items/index.tsx b/packages/antd/src/array-items/index.tsx index 23273057cfb..7925285bf26 100644 --- a/packages/antd/src/array-items/index.tsx +++ b/packages/antd/src/array-items/index.tsx @@ -100,7 +100,11 @@ ArrayItems.displayName = 'ArrayItems' ArrayItems.Item = (props) => { const prefixCls = usePrefixCls('formily-array-items') return ( -
+
{}} + className={cls(`${prefixCls}-card`, props.className)} + > {props.children}
) diff --git a/packages/next/src/array-items/index.tsx b/packages/next/src/array-items/index.tsx index a8f7d1dc39d..856288e8f6c 100644 --- a/packages/next/src/array-items/index.tsx +++ b/packages/next/src/array-items/index.tsx @@ -99,7 +99,11 @@ ArrayItems.displayName = 'ArrayItems' ArrayItems.Item = (props) => { const prefixCls = usePrefixCls('formily-array-items') return ( -
+
{}} + className={cls(`${prefixCls}-card`, props.className)} + > {props.children}
)