先看下 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 (
<span>
<Input
type="text"
size={size}
value={number}
onChange={this.handleNumberChange}
style={{ width: '65%', marginRight: '3%' }}
/>
<Select
value={currency}
size={size}
style={{ width: '32%' }}
onChange={this.handleCurrencyChange}
>
<Option value="rmb">RMB</Option>
<Option value="dollar">Dollar</Option>
</Select>
</span>
);
}
}
官方示例中有个函数需要引起我们的注意 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<DimensionInputProps, DimensionInputState, any> {
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 (
<div className={styles.dimensionInput}>
{Object.keys(options).map((dimensionCode: string) => (
<Row className={styles.dimensionInputItem} key={dimensionCode}>
<Col span={5}>
<Checkbox
className="ra"
checked={dimensionValueMap[dimensionCode].checked}
onChange={event => this.checkOptions(event, options[dimensionCode].value)}
>
<span>{options[dimensionCode].label}</span>
</Checkbox>
</Col>
<Col span={18} offset={1}>
<RaMulitCheckSelect
allowClear
placeholder="勾选左侧多选框后才可选择内容"
disabled={!(dimensionValueMap[dimensionCode] || { checked: false }).checked}
value={
Array.isArray((dimensionValueMap[dimensionCode] || { value: [] }).value)
? ((dimensionValueMap[dimensionCode] || { value: [] }).value || []).join(',')
: ''
}
dataSource={options[dimensionCode].options}
onChange={(value: string) => this.changeValue(dimensionCode, value)}
></RaMulitCheckSelect>
</Col>
</Row>
))}
</div>
);
}
}
export default DimensionInput;
最后要说明一下,这个自定义组件的方式会有一个小缺陷,当使用Form
表单的getFieldDecorator
时如果想另外使用onChange
来监测自定义组件数据是否有变化时会与Form
表单相冲突。所以,可以另外再定义一个回调,或者使用Form
的onValuesChange
来处理