order: 2 group: order: 3 title: 高级用法

toc: content

自定义组件(widget)

当 FormRender 提供的组件无法 100%满足用户需求时,可以考虑自己写一个。自定义组件功能使 FormRender 拥有很好扩展性,可能的应用场景如下:

  • 我需要写一个异步加载的搜索输入框(普适性不高/难以用 schema 描述的组件)
  • 我们团队使用 xxx ui,与 antd 不搭,希望能适配一套 xxx ui 组件的 FormRender(欢迎 Pull Request)
  • 我需要在表单内部写一个 excel 上传按钮(完全定制化的需求)

注:如果是新增一个常用组件,建议给 FormRender 维护的同学来提 Pull Request,这样可以更好扩展其生态,FormRender 的社区以及提供了部分 常用自定义组件

使用

简单的说,在 Form 组件层使用 widgets 字段注册自定义组件,并在 schema 内使用 widget 字段指明使用的组件 key 值即可:

  1. const schema = {
  2. type: 'object',
  3. properties: {
  4. string: {
  5. title: '网址输入自定义组件',
  6. type: 'string',
  7. widget: 'site',
  8. },
  9. //...
  10. },
  11. };
  12. <Form
  13. schema={schema}
  14. widgets={{ site: SiteInput }}
  15. //...
  16. />;

实际代码如下:

  1. import React from 'react';
  2. import { Input, Button } from 'antd';
  3. import Form, { useForm } from 'form-render';
  4. const schema = {
  5. type: 'object',
  6. properties: {
  7. string: {
  8. title: '网址输入自定义组件',
  9. type: 'string',
  10. widget: 'site',
  11. },
  12. select: {
  13. title: '单选',
  14. type: 'number',
  15. enum: [1, 2, 3],
  16. enumNames: ['选项1', '选项2', '选项3'],
  17. },
  18. },
  19. };
  20. const SiteInput = props => {
  21. console.log('widget props:', props);
  22. return <Input addonBefore="https://" addonAfter=".com" {...props} />;
  23. };
  24. const Demo = () => {
  25. const form = useForm();
  26. return (
  27. <div>
  28. <Form
  29. form={form}
  30. schema={schema}
  31. widgets={{ site: SiteInput }}
  32. onFinish={formData => alert(JSON.stringify(formData, null, 2))}
  33. />
  34. <Button type="primary" onClick={form.submit}>
  35. 提交
  36. </Button>
  37. </div>
  38. );
  39. };
  40. export default Demo;

不需要自己手写自定义组件哦

自定义组件就是普通的 React 组件,唯一的要求是要有 value/onChange 这两个 props,用于双向绑定值。所以如果现成的组件已经默认使用了 value/onChange,就可以直接拿来用。

举例来说:现在我们需要使用“级联选择”组件,FormRender 并没有内置支持。这时打开 antd 文档,我们看到 cascader 默认使用了 value/onChange,那就直接拿来用吧:

  1. import { Cascader } from 'antd';
  2. // 顶层引入注册
  3. ...
  4. <Form
  5. form={form}
  6. schema={schema}
  7. widgets={{ cascader: Cascader }}
  8. />
  9. // schema 中使用
  10. location: {
  11. title: '省市区',
  12. type: 'string',
  13. widget: 'cascader',
  14. props: {
  15. ...
  16. }
  17. },

自定义组件收到的 props

使用自定义组件时,大多有复杂定制需求,FormRender 提供了丰富的 props:

  • disabled:是否禁止输入
  • readOnly:是否只读
  • value:组件现在的值
  • onChange:函数,接收 value 为入参,用于将自定义组件的返回值同步给 Form
  • schema:组件对应的子 schema

addons 上挂着几乎所有的 form 方法:

  1. const {
  2. setValueByPath, // (path, value) => void
  3. getSchemaByPath, // (path) => schema
  4. setSchemaByPath, // (path) => void
  5. setSchema, // ({path1: schema1, path2: schema2}) => void
  6. setValues, // (newData) => void
  7. getValues, // () => formData
  8. resetFields, // () => void
  9. setErrorFields, // (errors) => void
  10. removeErrorField, // () => void
  11. } = addons;

详见开始使用的“form 方法”

  • addons.dataPath: 目前数据所在的 path,例如”a.b[2].c[0].d”,string 类型。
  • addons.dataIndex: 如果 dataPath 不包含数组,则为 [], 如果 dataPath 包含数组,例如”a.b[2].c[0].d”,则为 [2,0]。是自上到下所有经过的数组的 index 按顺序存放的一个数组类型
  • addons.dependValues: 当自定义组件对应的 schema 使用到 dependencies 字段时,在此获得 dependencies 对应的表单项的实时的值

antd 组件改造成自定义组件

大多数情况下,antd 的组件可以拿来即用。但有时组件的 props 并不是约定的 value/onChange, 例如 Checkbox 的情况,value 值对应的是 checked,此时只需要少量改动即可:

  1. import { Checkbox } from 'antd';
  2. const MyCheckBox = ({ value, ...rest }) => {
  3. return <Checkbox checked={value} {...rest} />;
  4. };

只读模式下的自定义组件

只读模式下,默认会渲染内置的 html 组件,但有时 html 组件并不能满足一个自定义组件在只读模式下需要的展示,此时可使用readOnlyWidget字段来指定只读模式下的展示。

  1. {
  2. "type": "object",
  3. "properties": {
  4. "string": {
  5. "title": "网址输入自定义组件",
  6. "type": "string",
  7. "widget": "site",
  8. "readOnlyWidget": "siteText"
  9. }
  10. }
  11. }

如果你打算在一个自定义组件里通过 readOnly 参数判断条件展示,既是说,site 组件已经写了只读和非只读情况下的渲染

  1. const SiteInput = ({ readOnly, value, ...rest }) => {
  2. if (readOnly) return <div>{`https://${value}.com`}</div>;
  3. return (
  4. <Input addonBefore="https://" addonAfter=".com" value={value} {...rest} />
  5. );
  6. };

此时可以指定 readOnlyWidgetwidget 为同一个组件:

  1. {
  2. "type": "object",
  3. "properties": {
  4. "string": {
  5. "title": "网址输入自定义组件",
  6. "type": "string",
  7. "widget": "site",
  8. "readOnlyWidget": "site"
  9. }
  10. }
  11. }

最佳实践

同一个项目下不同的 form 里,使用到的自定义组件可能大致相同,但也有可能互相不同,笔者建议是中心化一个 Form 组件,并一次性将所有需要的自定义组件注入其中。在项目的各处引入对应组件:

  1. // /Component/FormRender.js
  2. import Form from 'form-render';
  3. import Cascade from './Cascade';
  4. import Percentage from './Percentage';
  5. import MyCheckBox from './MyCheckBox';
  6. import ExcelUploader from './ExcelUploader';
  7. const FormRender = props => {
  8. return (
  9. <Form
  10. widgets={{
  11. percentage: Percentage,
  12. cascade: Cascade,
  13. myCheck: MyCheckBox,
  14. excelUpload: ExcelUploader,
  15. }}
  16. {...props}
  17. />
  18. );
  19. };

然后在每个 form 页面统一引入使用

  1. import { useForm } from 'form-render';
  2. import FormRender from './Component/FormRender';
  3. const Demo1 = props => {
  4. const form = useForm();
  5. return <FormRender form={form} onFinish={() => {}} />;
  6. };

内置组件

使用自定义组件前,也许已经有内置组件支持。具体见 schema 与内置组件