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

使用
官方示例:
import React, { Component } from 'react';import { withTheme } from 'react-jsonschema-form';// demo themeimport Bootstrap4Theme from 'react-jsonschema-form-theme-bs4';const ThemedForm = withTheme(Bootstrap4Theme);// use ThemedFormclass 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来指定数据类型,包括:
stringnumberintegerboolean
任何定制化的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的defaultOptionsformContext: 传递给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列表如下:
ArrayFieldBooleanFieldDescriptionFieldMultiSchemaFieldNullFieldNumberFieldObjectFieldSchemaFieldStringFieldTitleFieldUnsupportedField
模板
通过自定义模板,我们可以改变单个或多个表单项的布局,另外可以基于这种能力,我们可以更好地控制表单的布局,通过参数来调整表单的布局,如指定表单水平方向或垂直方向布局。
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;
当然,上面的代码目前仅仅刚跑起来,等后面优化完会放出的哦。记得关注我的公号【也寻常】,顺便聊点别的~
