先看下 antd 官网自定义(第三方)表单组件的写法:
import { Form, Input, Select, Button } from 'antd';
const { Option } = Select;
class PriceInput extends React.Component {
static getDerivedStateFromProps(nextProps) {
// Should be a controlled component.
if ('value' in nextProps) {
return {
...(nextProps.value || {}),
};
}
return null;
}
constructor(props) {
super(props);
const value = props.value || {};
this.state = {
number: value.number || 0,
currency: value.currency || 'rmb',
};
}
handleNumberChange = e => {
const number = parseInt(e.target.value || 0, 10);
if (isNaN(number)) {
return;
}
if (!('value' in this.props)) {
this.setState({ number });
}
this.triggerChange({ number });
};
handleCurrencyChange = currency => {
if (!('value' in this.props)) {
this.setState({ currency });
}
this.triggerChange({ currency });
};
triggerChange = changedValue => {
// Should provide an event to pass value to Form.
const { onChange } = this.props;
if (onChange) {
onChange({
...this.state,
...changedValue,
});
}
};
render() {
const { size } = this.props;
const { currency, number } = this.state;
return (
);
}
}
官方示例中有个函数需要引起我们的注意 getDerivedStateFromProps
这个函数的意思就是吧props
中传入的参数注入到state
中。
这个函数中的代码块的意思是当props
中有value
时,往state
中注入nextProps.value
,如果没有则返回null,不往state
中注入数据。
当我们更改了数据时,需要往组件外传递修改后的值,一般就是通过onChange
回调来做。所以,有一个在这个示例中有一个triggerChange
函数专门来处理这个事情。这里也是判断如果有这个回调函数就调用onChange
。
不过,为什么数据变化的时候会需要先有下面的操作呢?
if (!('value' in this.props)) {
this.setState({ currency });
}
我觉得是为了防止重复渲染
因为setState
会触发视图渲染,而只要props
中有value
就会在getDerivedStateFromProps
中执行一次state
的变化。而onChange
回调会改变props
中value
的值,因此如果不做这个判断就会导致重复渲染了。
下面给个我自己写的示例:
先看需求
未勾选状态
勾选状态
/**
* 统计维度自定义表单组件
*
*/
import React from 'react';
import { Checkbox, Row, Col, Select } from 'antd';
import RaMulitCheckSelect from '@/components/RaMulitCheckSelect';
// 这些都是定义的数据格式类型,不用在意
import { SelectOption, DimensionValue, DimensionValueMap, DimensionMap } from '@/utils/interfaces';
import styles from './dimensionInput.less';
// 维度输入组件的state
interface DimensionInputState {
dimensionValueMap: DimensionValueMap;
}
// 维度输入组件的props
interface DimensionInputProps {
value?: DimensionValueMap | undefined;
options?: DimensionMap;
onChange?: Function;
}
function getDimensionMapModel(): DimensionMap {
return {
组织: {
label: '组织',
value: '组织',
options: [
{ label: '总部', value: '总部' },
{ label: '大区', value: '大区' },
{ label: '地区', value: '地区' },
{ label: '分部', value: '分部' },
{ label: '点部', value: '点部' },
],
},
};
}
// 初始化value
function initValue(
dimensionValueMap: DimensionValueMap,
options = getDimensionMapModel(),
): DimensionValueMap {
Object.keys(options).forEach(key => {
if (!dimensionValueMap[key]) {
dimensionValueMap[key] = {
checked: false,
value: [],
};
}
});
return dimensionValueMap;
}
const { Option } = Select;
class DimensionInput extends React.PureComponent {
static getDerivedStateFromProps(nextProps: DimensionInputProps) {
if ('value' in nextProps) {
// 如果props中有value则替代state中的dimensionValueMap
return {
dimensionValueMap: initValue(nextProps.value || {}, nextProps.options),
};
}
return null;
}
constructor(props: DimensionInputProps) {
super(props);
const dimensionValueMap = props.value || {};
// 初始化dimensionValueMap
this.state = {
dimensionValueMap: initValue(props.value || {}, props.options),
};
}
// 这里也是一个自定义的组件 RaMulitCheckSelect 的value发生变化时的数据收集
changeValue = (key: string, value: string) => {
const valueList = (value || '').split(',').filter(v => !!v);
const { dimensionValueMap } = this.state;
this.triggerChange({ ...dimensionValueMap, [key]: { checked: true, value: valueList } });
};
// 当数据发生变化时
triggerChange = (dimensionValueMap: DimensionValueMap) => {
const { onChange } = this.props;
if (!('value' in this.props)) {
this.setState({ dimensionValueMap });
}
if (onChange) {
onChange(dimensionValueMap);
}
};
// 当勾选某一个维度时
checkOptions = (event: any, key: string) => {
const checked = event.target.checked;
const { dimensionValueMap } = this.state;
if (!dimensionValueMap[key]) dimensionValueMap[key] = { checked: false, value: [] };
dimensionValueMap[key].checked = checked;
if (!checked) dimensionValueMap[key].value = [];
this.triggerChange({ ...dimensionValueMap });
};
render() {
const { dimensionValueMap } = this.state;
const { options = getDimensionMapModel() } = this.props;
return (
{Object.keys(options).map((dimensionCode: string) => (
this.checkOptions(event, options[dimensionCode].value)}
>
{options[dimensionCode].label}
this.changeValue(dimensionCode, value)}
>
))}
);
}
}
export default DimensionInput;
最后要说明一下,这个自定义组件的方式会有一个小缺陷,当使用Form
表单的getFieldDecorator
时如果想另外使用onChange
来监测自定义组件数据是否有变化时会与Form
表单相冲突。所以,可以另外再定义一个回调,或者使用Form
的onValuesChange
来处理