File size: 10,204 Bytes
c9d0005
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
---
order: 2
toc: content
mobile: false
group: 
  title: 高级用法
  order: 1
---

# 自定义组件

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

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

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

使用自定义组件前,也许已经有内置组件支持。具体见 [内置组件](/form-render/display-row)

:::info
如果是新增一个常用组件,建议给 FormRender 维护的同学来提 Pull Request 或 Issue 并说明你的使用场景,这样可以更好扩展其生态,FormRender 的社区以及提供了部分 [常用自定义组件](https://github.com/alibaba/x-render/tree/master/widgets)。
:::

## 什么是 Widget

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

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

```js
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。

```js
import CaptchaInput from 'my/widgets';

<FormRender widgets={{ CaptchaInput }} />
```

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

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

完整代码如下:

<code src="./demo/widget/basic.tsx"></code>

## 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](/form-render/api-props#forminstance),这里不再赘述。除此之外 addons 还包含了如下一些额外属性。

#### addons.globalProps
- 类型:`Record<string, any>`
- 描述:全局属性

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

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

#### addons.dataPath
- 类型:`string`
- 描述:目前数据所在的 path,例如`a.b[2].c[0].d`,详见 [Path 书写](/form-render/advanced-path)

#### addons.schemaPath
- 类型:`string`
- 描述:当前 item 的 schema 在整体中的路径

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

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

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

```js
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 的数据。详见 [表单联动](/form-render/advanced-linkage)

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

完整示例如下:

<code src="./demo/widget/linkage.tsx"></code>


## 使用 dependencies 联动
除了使用表达式联动,widget 还可以使用 `dependencies` 属性进行联动。首先在 schema 中定义好 `dependencies`,比如:
```js
const shcema = {
    type: 'object',
    properties: {
        age: {
            title: '年龄',
            type: 'string',
        },
        name: {
            title: '姓名',
            type: 'string',
            widget: 'MyInput',
            // 指定依赖的字段
            dependencies: ['age']
        },
    }
}
```

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

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

    return (
        // ...
    )
}
```
与上面同样的例子,使用 `dependencies` 的代码如下:
<code src="./demo/widget/depend-linkage.tsx"></code>

:::info
`dependencies` 除了触自动更新之外,还能触发校验,详见 [表单联动](/form-render/advanced-linkage#dependencies-依赖字段)
:::

## 其他 Widget

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

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

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

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

```js
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} />
  );
};
```

此时可以指定 `readOnlyWidget``widget` 为同一个组件:

```js
const schema = {
  type: 'object',
  properties: {
    string: {
      title: 'ReadOnly widget',
      type: 'string',
      widget: 'SiteInput',
      readOnlyWidget: 'SiteInput',
    },
  },
};
```
完整代码如下:

<code src="./demo/widget/readonly-widget.tsx"></code>

### labelWidget

使用 `labelWidget` 自定义 label 组件,此时 widget 接收到的 props 只有 `schema`<code src="./demo/widget/label-widget.tsx"></code>

### descWidget

使用 `descWidget` 自定义 description 组件,此时 widget 接收到的 props 只有 `schema`<code src="./demo/widget/desc-widget.tsx"></code>

## 统一管理 Widget

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

```js
//  /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 页面统一引入使用

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

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

```ts
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;
```