技术/前端/React
最近在调研react-jsonschema-form
,想通过这个来简化form表单的开发,另外是想通过配置化的方式来快速生成不同表单项。但是经过调研发现,rjsf本身是用bootstrap-v3来渲染的,不过它也提供了withTheme
的方式来重写或者定制widgets/fields
,也可以覆盖默认表单组件的props。

使用
官方示例:
import React, { Component } from 'react';
import { withTheme } from 'react-jsonschema-form';
// demo theme
import Bootstrap4Theme from 'react-jsonschema-form-theme-bs4';
const ThemedForm = withTheme(Bootstrap4Theme);
// use ThemedForm
class Demo extends Component {
render() {
return <ThemedForm {...props}/>
}
}
高级自定义方案介绍
react-jsonschema-form
提供了不同方式的定制方法,可以针对不同场景来实现定制,具体应用参考下表:
Theme参数
withTheme
是一个高阶组件,并且接收唯一一个对象参数(ThemeObj),该组件会返回一个经过主题改变后的组件来替换rjsf提供的标准Form
组件。
首先,ThemeObj
的属性和标准Form组件的属性一样,定制后的Form组件会将ThemeObj
对象属性和标准Form组件的默认属性合并,如widgets
和fields
。
如果参入的属性名称与默认的同名,则会将会属性合并,如果不指定widget或者field则会使用默认的。
Widgets & fields
- widget: 代表一个HTML标签,用于用户输入数据,如
input
、select
等 - field: 通过使用一个或多个widget组合来处理字段内部状态,可以理解为一个field代表表单中的一项
所以,如果我们想要覆盖标准的日期输入框,则可以自定义一个DateWidget,然后传给ThemeObj
,而Field也是一样。
const MyCustomDateWidget = (props) => {
return (
<Datepicker
className="custom"
value={props.value}
required={props.required}
onChange={(event) => props.onChange(event.target.value)} />
);
};
const myWidgets = {
myCustomWidget: MyCustomDateWidget
};
const ThemeObject = {widgets: myWidgets};
export default ThemeObject;
Widgets
一个widget可以通过uiSchema
来指定数据类型,包括:
string
number
integer
boolean
任何定制化的widget组件都会接收如下属性:
id
: 该字段的id(自动生成)schema
: 当前字段的schema对象(局部)value
: 当前字段的值placeholder
: 可选,占位符required
: 是否必须disabled
: 是否disabled,true/falsereadonly
: 是否只读autofocus
: 是否默认autofocusonChange
: change事件回调onBlur
: blur事件回调,参数: (id, value)onFocus
: focus事件回调,参数:(id, value)options
: 组件额外参数,通过uiSchema
传递或widget的defaultOptions
formContext
: 传递给Form组件的formContext对象属性,会传递到自定义的widget
可以自定义的widgets列表如下:
- AltDateTimeWidget
- AltDateWidget
- CheckboxesWidget
- CheckboxWidget
- ColorWidget
- DateTimeWidget
- DateWidget
- EmailWidget
- FileWidget
- HiddenWidget
- PasswordWidget
- RadioWidget
- RangeWidget
- SelectWidget
- TextareaWidget
- TextWidget
- UpDownWidget
- URLWidget
可参考源码了解各个Widget的实现:https://github.com/rjsf-team/react-jsonschema-form/tree/master/packages/core/src/components/widgets
Fields
我们可以通过给uiSchema
指定ui:field
属性来实现任何自定义的field组件,比如可以创建一个并且注册一个geo
组件来处理latitude
和longitude
表单项
// 定义schema
const schema = {
type: "object",
required: ["lat", "lon"],
properties: {
lat: {type: "number"},
lon: {type: "number"}
}
};
// Define a custom component for handling the root position object
class GeoPosition extends React.Component {
constructor(props) {
super(props);
this.state = {...props.formData};
}
onChange(name) {
return (event) => {
this.setState({
[name]: parseFloat(event.target.value)
}, () => this.props.onChange(this.state));
};
}
render() {
const {lat, lon} = this.state;
return (
<div>
<input type="number" value={lat} onChange={this.onChange("lat")} />
<input type="number" value={lon} onChange={this.onChange("lon")} />
</div>
);
}
}
// Define the custom field component to use for the root object
const uiSchema = {"ui:field": "geo"};
// Define the custom field components to register; here our "geo"
// custom field component
const fields = {geo: GeoPosition};
render((
<Form
schema={schema}
uiSchema={uiSchema}
fields={fields} />
), document.getElementById("app"));
上面代码逻辑:
- 定义jsonschema,指定字段名称及其他配置
- 定义一个field组件处理对应字段
- 指定
ui:field
交给geo处理 - 注册geo为一个field
注意
: 注册过的字段可以整个schema中使用
Field会接收如下参数:
schema
: 当前field的son schemauiSchema
:当前field的uiSchemaidSchema
: 子field的标识id树,针对ArrayField使用formData
: 该字段的数据errorSchema
: 错误提示(当前字段及子字段),针对ArrayField使用registry
: 包含所有注册过的widgets和fields、definitions和formContextformContext
: 该属性会自动传递给所有的fields和widgets
可以自定义的fields列表如下:
ArrayField
BooleanField
DescriptionField
MultiSchemaField
NullField
NumberField
ObjectField
SchemaField
StringField
TitleField
UnsupportedField
模板
通过自定义模板,我们可以改变单个或多个表单项的布局,另外可以基于这种能力,我们可以更好地控制表单的布局,通过参数来调整表单的布局,如指定表单水平方向或垂直方向布局。
Field模板
Field Tempalte
赋予我们控制各个field内部(即每个表单项)的布局能力,通常一个field template基本是一个React 无状态组件,接收field相关的props,从而可以重新组织单个表单项的内部结构
定义一个CustomFieldTemplate
:
function CustomFieldTemplate(props) {
const {id, classNames, label, help, required, description, errors, children} = props;
return (
<div className={classNames}>
<label htmlFor={id}>{label}{required ? "*" : null}</label>
{description}
{children}
{errors}
{help}
</div>
);
}
上面看出我们改变了label
、description
、children
、errors
以及help
的布局,当然我们也可以实现其他形式。另外,针对description
、errors
和help
,我们也可以使用rawDescription
、rawErrors
、rawHelp
来控制元素的渲染过程。
应用方法1:
<Form schema={schema} FieldTemplate={CustomFieldTemplate}/
应用方法2:
const uiSchema = {
"ui:FieldTemplate": CustomFieldTemplate
}
<Form schema={schema} uiSchema={uiSchema}/>
应用方法3:
import { withTheme } from 'react-jsonschema-form'
const Theme = {
FieldTemplate: CustomFieldTemplate
}
const Form = withTheme(Theme)
自定义的field template接收如下参数:
https://react-jsonschema-form.readthedocs.io/en/latest/advanced-customization/#custom-widgets-and-fields
id
: 当前field的id,可用来渲染指定该id的labelclassNames
: 默认是Bootstrap Css 类名字符串,也会将uiSchema
中自定义的classNames合并进行来label
: 该field对应的label字符串description
: 该field的description渲染后的组件实例(如果有自定义的DescriptionField)rawDescription
:ui:description
定义的字符串children
: 该field的field或widget组件实例errors
: 该字段的错误提示信息的组件实例列表rawErrors
: 该字段的原始错误提示信息列表help
:ui:help
定义的帮助信息组件实例rawHelp
:ui:help
定义的原始帮助信息字符串hidden
: 是否隐藏该fieldrequired
: 是否必须readonly
: 是否只读disabled
: 是否disableddisplayLabel
: 是否渲染label,在多个field嵌套时可以根据该属性来控制label是否渲染fields
: 所有默认和自定义的fieldsschema
: 该字段的schema对象uiSchema
: 该对象的uiSchema对象formContext
: Form组件的formContext属性
一个Form的全局field template只能有一个,但是可以通过ui:FieldTemplate
的方式指定不同的field template。
Array Field Template
与FieldTemplate
类似,通过ArrayFieldTemplate
可以定制数组项目的布局。
实现一个自定义布局的CustomArrayFieldTemplate
:
function ArrayFieldTemplate(props) {
return (
<div>
{props.items.map(element => element.children)}
{props.canAdd && <button type="button" onClick={props.onAddClick}></button>}
</div>
);
}
应用方案可参考FieldTemplate
。
每个ArrayFieldTemplate
会接收如下属性:
DescriptionField
: 注册的DescriptionField
,在需要的地方可以使用TitleField
: 注册的TitleField
,在需要的地方可以使用canAdd
: 是否可以新增数组元素className
: className字符串disabled
: 是否disabledidSchema
: 对象items
: 数组项,每个元素为一个对象(具体格式如下)onAddClick: (event) => void
: 新增数组项事件回调readonly
: 是否只读required
: 是否必须schema
: 该数组的schema对象uiSchema
: 该数组的uiSchema对象title
: 该数组field的标题formContex
: 全局formContextformData
: 该数组的表单数据
上面的items
属性,其每个元素包括如下属性:
children
: item的html内容className
: className字符串disabled
: 是否disabledhasMoveDown
: 是否可向下移动hasMoveUp
: 是否可向上移动hasToolbar
: 是否有工具条index
: 数组下标key
: 该item的keyonAddIndexClick: (index) => (event) => void
: 新增一个数组项回调onDropIndexClick: (index) => (event) => void
: 删除一哥数组项回调onReorderClick: (index, newIndex) => (event) => void
: 交换两个数组项回调readonly
: 是否只读
注意
: 无论是ArrayFieldTemplate
还是ObjectFieldTemplate
,都是在FieldTemplate
内渲染的,因此想要定制ArrayFieldTemplate
或ObjectFieldTemplate
,应该先实现FieldTemplate
。
Object Field Template
跟FieldTemplate
类似,ObjectFieldTemplate
用来组织对象格式的渲染。
function ObjectFieldTemplate(props) {
return (
<div>
{props.title}
{props.description}
{props.properties.map(element => <div className="property-wrapper">{element.content}</div>)}
</div>
);
}
应该方式参考FieldTemplate
。
每个ObjectFieldTemplate
可接收如下参数:
DescriptionField
: 注册的DescriptionField
,在需要的地方可以使用TitleField
: 注册的TitleField
,在需要的地方可以使用properties
: 对象的属性数组,具体个数如下disabled
: 是否disabledreadonly
: 是否只读required
: 是否必须schema
: 该数组的schema对象idSchema
: 对象uiSchema
: 该数组的uiSchema对象title
: 该对象field的标题description
: 该对象field的描述formContex
: 全局formContextformData
: 该对象field的表单数据
properties
的每项:
content
: 当前项的html内容name
: 当前项的属性名称disabled
: 是否disabledreadonly
: 是否只读
ErrorListTemplate
想要控制表单错误内容如何显示,可以定义一个error list template,请了解该错误列表是全局的,会显示在你的表单之上,但我们也可以通过具体参数来控制是否显示错误列表。
我们可以定义个无状态组件并且接收errors
数组属性,然后将errors渲染出来:
function ErrorListTemplate(props) {
const {errors} = props;
return (
<ul>
{errors.map(error => (
<li key={error.stack}>
{error.stack}
</li>
))}
</ul>
);
}
应用方式同样参考FieldTemplate
。
另外,可以指定showErrorList
来控制是否显示ErrorListTemplate
。
const Theme = {
ErrorList={ErrorListTemplate}
showErrorList: false
}
ErrorListTemplate
除了接收errors
属性外,还接收以下参数:
errorSchema
: 由Form组件生成schema
: 传给Form的schemauiSchema
: 传给Form的uiSchemaformContext
: 传给Form的formContext
定制Antd主题
通过提供antd版本的widgets和fields来实现定制,看下项目目录:
针对不同的field或widget可以通过重新定义组件来实现定制化,如CheckboxWidget
:
import React from 'react';
import { Checkbox } from 'antd';
import { WidgetProps } from 'react-jsonschema-form';
const CheckboxWidget = (props: WidgetProps) => {
const {
id,
value,
required,
disabled,
readonly,
label,
autofocus,
onChange,
onBlur,
onFocus,
} = props;
const _onChange = ({
target: { value },
}: React.ChangeEvent<HTMLInputElement>) => onChange(value);
const _onBlur = ({
target: { value },
}: React.FocusEvent<HTMLButtonElement>) => onBlur(id, value);
const _onFocus = ({
target: { value },
}: React.FocusEvent<HTMLButtonElement>) => onFocus(id, value);
return (
<Checkbox
id={id}
checked={typeof value === 'undefined' ? false : value}
required={required}
disabled={disabled || readonly}
autoFocus={autofocus}
onChange={_onChange}
onBlur={_onBlur}
onFocus={_onFocus}
>{label}</Checkbox>
);
};
export default CheckboxWidget;
使用withTheme
:
import FieldTemplate from '../Templates/FieldTemplate';
*// import ArrayFieldTemplate from '../Templates/ArrayFieldTemplate';*
import ObjectFieldTemplate from '../Templates/ObjectFieldTemplate';
import ErrorListTemplate from '../Templates/ErrorListTemplate';
import Fields from '../Fields';
import Widgets from '../Widgets';
import { ThemeProps } from 'react-jsonschema-form';
// 默认提供的fields及widgets
import { getDefaultRegistry } from 'react-jsonschema-form/lib/utils';
const { fields, widgets } = getDefaultRegistry();
const Theme: ThemeProps = {
fields: { ...fields, ...Fields },
widgets: { ...widgets, ...Widgets },
FieldTemplate,
*// ArrayFieldTemplate,*
ObjectFieldTemplate,
ErrorList: ErrorListTemplate,
};
export default Theme;
当然,上面的代码目前仅仅刚跑起来,等后面优化完会放出的哦。记得关注我的公号【也寻常】,顺便聊点别的~