x-render / docs /form-render /advanced-widget.md
AbdulElahGwaith's picture
Upload folder using huggingface_hub
c9d0005 verified
|
Raw
History Blame Contribute Delete
10.2 kB
metadata
order: 2
toc: content
mobile: false
group:
  title: 高级用法
  order: 1

自定义组件

在实际的开发中,可能会遇到如下的应用场景:

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

FormRender 内置的控件可能不能满足功能上的需要,这时就需要自定义组件 widget 的支持

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

:::info 如果是新增一个常用组件,建议给 FormRender 维护的同学来提 Pull Request 或 Issue 并说明你的使用场景,这样可以更好扩展其生态,FormRender 的社区以及提供了部分 常用自定义组件。 :::

什么是 Widget

widget 只是一个普通的 React 组件,它会接收到 FormRender 传递给它的一些 props。开发者可以根据这些 props 完成控件的受控、联动、校验等操作。

比方说,我想在一个常规输入框的后面放一个按钮用于发送验证码。FormRender 的内置组件不能满足需求,那么我可以写一个如下的自定义组件:

const CaptchaInput = (props: any) => {
  const { value, onChange } = props;
  console.log('widget props:', props);

  const sendCaptcha = (phone: string) => {
    console.log('send captcha to:', phone);
  }

  return (
    <Space>
      <Input
        value={value}
        onChange={(e) => onChange(e.target.value)}
        placeholder="请输入手机号"
      />
      <Button onClick={() => sendCaptcha(value)}>发送验证码</Button>
    </Space>
  );
};

使用 Widget

首先在 <FormRender /> 中注册 widget。

import CaptchaInput from 'my/widgets';

<FormRender widgets={{ CaptchaInput }} />

之后在 Schema 中指定 item 的 widget 属性为刚刚注册的 widget。

const schema = {
  type: 'object',
  properties: {
    phone: {
      title: '网址输入自定义组件',
      type: 'string',
      // 指定为刚刚注册的 widget
      widget: 'CaptchaInput',
    }
  }
};

完整代码如下:

Widget 接收到的 props

默认情况下 Widget 会接收到如下的 props:

id

  • 类型:string
  • 描述:当前 item 在表单中的唯一 key,一般用不到

schema

  • 类型:Schema
  • 描述:当前 item 的 schema

value

  • 类型:any
  • 描述:当前 item 的值,用于 widget 的受控

onChange

  • 类型:(value: any) => void
  • 描述:当前 item 的值变化时的回调用于 widget 的受控

disabled

  • 类型:boolean
  • 描述:当前 item 是否为禁用状态,如果没有单独为这个 item 指定,那么就继承全局的 disabled 属性

readOnly

  • 类型:boolean
  • 描述:当前 item 是否为只读状态,如果没有单独为这个 item 指定,那么就继承全局的 readOnly 属性

addons

addons 包含了全部的表单实例方法,详见 FormInstance,这里不再赘述。除此之外 addons 还包含了如下一些额外属性。

addons.globalProps

  • 类型:Record<string, any>
  • 描述:全局属性

addons.dataIndex

  • 类型:string[]
  • 描述:是自上到下所有经过的数组的 index 按顺序存放的一个数组类型。例如当前的 dataPatha.b[2].c[0].d,那么这时的 dataIndex 就为 [2,0]

:::info 如果不是在 Form List 中,那么 dataIndex 始终为一个空数据 []。 :::

addons.dataPath

  • 类型:string
  • 描述:目前数据所在的 path,例如a.b[2].c[0].d,详见 Path 书写

addons.schemaPath

  • 类型:string
  • 描述:当前 item 的 schema 在整体中的路径

addons.dependValues

  • 类型:any[]
  • 描述:当自定义组件对应的 schema 使用到 dependencies 字段时,在此获得 dependencies 对应的表单项的实时的值

others

实际上任何写入当前 schema 的 props 中的属性都会透传给 widget,所以你不必把所有自定义的属性都写在 widget 内部,更好的方式是通过 schema 控制,以得到更好的复用性。

:::error 在编写 props 时请避开 valueonChangeaddons 等字段防止 FormRender 注入的 props 被覆盖。 :::

const shcema = {
    type: 'object',
    properties: {
        name: {
            title: '姓名',
            type: 'string',
            widget: 'MyInput',
            // props 中的属性都会透传给自定义组件 MyInput
            props: {
                addonAfter: 'name',
                allowClear: true,
                foo: 'xxx',
            }
        }
    }
}

使用表达式联动

表达式是 FormRender 实现简单联动的一个方式。上面提到 widget 会接收到 props 中的属性,这其中也包括使用表达式的字段。FormRender 会先表达式根据表单状态表达式进行转换,然后传递给 widget,并且实时的更新这个属性。我们可以利用这一点很方便的实现一些表单联动。

formData 关键字当前全部的表单状态,对于 Form List 的场景,使用 rootValue 关键字来表示当前 List Item 的数据。详见 表单联动

const shcema = {
    type: 'object',
    properties: {
        age: {
            title: '年龄',
            type: 'string',
        },
        name: {
            title: '姓名',
            type: 'string',
            widget: 'MyInput',
            props: {
                // 当 age 字段更新时,自定义组件 MyInput 会接收到最新的 age 属性
                age: '{{ formData.age }}' 
            }
        },
    }
}

完整示例如下:

使用 dependencies 联动

除了使用表达式联动,widget 还可以使用 dependencies 属性进行联动。首先在 schema 中定义好 dependencies,比如:

const shcema = {
    type: 'object',
    properties: {
        age: {
            title: '年龄',
            type: 'string',
        },
        name: {
            title: '姓名',
            type: 'string',
            widget: 'MyInput',
            // 指定依赖的字段
            dependencies: ['age']
        },
    }
}

之后在 widget 的 props.addons.dependValues 中可以拿到依赖的值。

const MyInput = (props) => {
    const { addons } = props;
    console.log('dependValues:', addons.dependValues);
    // dependValues: ['xxxx']

    return (
        // ...
    )
}

与上面同样的例子,使用 dependencies 的代码如下:

:::info dependencies 除了触自动更新之外,还能触发校验,详见 表单联动 :::

其他 Widget

除了输入控件可以自定义 widget 之外,Form Render 还提供了自定义一个表单项其他部分的能力。

readOnlyWidget

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

const schema = {
  type: 'object',
  properties: {
    string: {
      title: 'ReadOnly widget',
      type: 'string',
      widget: 'SiteInput',
      readOnlyWidget: 'ReadOnlySiteInput',
    },
  },
};

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

const SiteInput = ({ readOnly, value, ...rest }: WidgetProps) => {
  if (readOnly) return <a href={`https://${value}.com`}>{`https://${value || ''}.com`}</a>;
  return (
    <Input addonBefore="https://" addonAfter=".com" value={value} {...rest} />
  );
};

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

const schema = {
  type: 'object',
  properties: {
    string: {
      title: 'ReadOnly widget',
      type: 'string',
      widget: 'SiteInput',
      readOnlyWidget: 'SiteInput',
    },
  },
};

完整代码如下:

labelWidget

使用 labelWidget 自定义 label 组件,此时 widget 接收到的 props 只有 schema

descWidget

使用 descWidget 自定义 description 组件,此时 widget 接收到的 props 只有 schema

统一管理 Widget

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

//  /Component/FormRender.js
import Form from 'form-render';
import Cascade from './Cascade';
import Percentage from './Percentage';
import MyCheckBox from './MyCheckBox';
import ExcelUploader from './ExcelUploader';

export default props => (
    <Form 
      widgets={{ 
        Percentage, 
        Cascade, 
        MyCheckBox, 
        ExcelUploader
      }}
      {...props}
    />
  );

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

import { useForm } from 'form-render';
import FormRender from './Component/FormRender';
const Demo = props => {
  const form = useForm();
  return <FormRender form={form} onFinish={() => {}} />;
};

使用 TS

使用 WidgetProps 获得自定义组件 props 的类型支持。

import { FC } from 'react';
import type { WidgetProps } from 'form-render';

const MyWidget: FC<WidgetProps> = (props) => {
    const { value, onChange, addons } = props;

    return (
        <div>My Widget</div>
    )
}

export default MyWidget;