最近在项目中需要开发这样一个动态的select组件功能,梳理一下需求:
1.左侧的select和右侧的select是一个联动效果,右侧的select有几个option取决于选择了左侧的某个option
2.左侧的select有几个option取决于后台接口返回的数据
3.当页面加载时,会去接口中读取已有的变量和成员表单,一行的变量和成员可以看做一个整体
4.页面的表单可以用户手动增删
在开发过程的遇到的问题和解决方案记录:
1.动态增删表单
原理是使用getFieldValue方法和setFieldsValue方法,通过增加keys数组成员和删除keys数组成员来达到增删表单的效果。
removeFormItem = (k) => {
const { form } = this.props;
const keys = form.getFieldValue('keys');
form.setFieldsValue({
keys: keys.filter(key => key !== k),
});
}
addFormItem = () => {
const { form } = this.props;
let keys = form.getFieldValue('keys');
//增加表单时,这个key必须唯一,这里我引入uuid这个库来生成唯一ID
//( import UUID from 'uuid/v1')
const nextKeys = keys.concat(UUID());
form.setFieldsValue({
keys: nextKeys,
});
}
2.如何使用后台动态获取的值去设置页面的form表单
这个一度成为我的难点,通过后台返回的已有变量列表值去设置初始页面的表单数量,比如后台返回3个,我需要设置三个select,那么这一步在什么时候做。
- 在render的时候去设置已有表单,因为动态获取的表单数量是由父组件传入,所以在子组件中用props接收。
- 在props中的taskVariableList属性读取每一项的id作为form的key值,遍历后放进一个formKeys的数组,这个数组就是已有表单的key值
- getFieldDecorator('keys', { initialValue:formKeys}) 重点是使用这个方法设置初始表单
const { getFieldDecorator, getFieldValue } = this.props.form;
let {taskVariableList,taskVariableOptionList}=this.props;
let formKeys=[],initValueObject={};
//使用已选列表每项的id作为已有表单的key值
if(taskVariableList.length>0){
taskVariableList.map(variable=>{
formKeys.push(variable.id);
//将已有选项的值 以id-选项值的key-value形式存入一个对象
initValueObject[variable.id]=variable
})
}else{
//如果没有已有列表 保证页面有一个初始的select
if(this.state.haveNoVariable){
formKeys.push(singleSelectUUID);
}
}
getFieldDecorator('keys', { initialValue:formKeys});
3.如何渲染表单组件
一切的数据获取,数据增删都是储存在form的keys属性里面,那么const keys = form.getFieldValue('keys'),使用这个keys数组去渲染表单,因为每次增删改查都是操作这个keys数组,所以通过keys总是渲染正确实时的表单数据
<Form style={{margin:"0px 12px"}} className="task-variable">
{
keys&&keys.map((key,index)=>{
return (
<Row gutter={24} key={key}>
<Col span={11}>
<FormItem
{...formItemLayout}
label="变量"
>
{getFieldDecorator(`variable#${key}`,{initialValue:initValueObject[key]?initValueObject[key]["fromDimensionId"]:initialLeftValue,})(
<Select onChange={this.handleNewVariableChange.bind(this,taskVariableOptionList,key)}>
{taskVariableOptionList.map(dimension=>{
return (
<Option value={dimension.fromDimensionId} key={`${dimension.id}-${index}`}>{dimension.name}</Option>
)
})}
</Select>
)}
</FormItem>
</Col>
<Col span={11}>
<FormItem
{...formItemLayout}
label="成员"
>
{this.renderSubItem(initValueObject,taskVariableOptionList,key,getFieldDecorator,initValueObject[key]?initValueObject[key]["fromCodeId"]:initialRightValue)}
</FormItem>
</Col>
<Col className="select-action-wrap" span={2}>
{keys.length ===(index+1)&&keys.length!==1? (
<i className="icon font_family" onClick={this.addFormItem}></i>
) : <Icon
className="dynamic-delete-button"
type="minus-circle-o"
disabled={keys.length === 1}
onClick={() => this.removeFormItem(key)}
/>}
</Col>
</Row>
)
})
}
</Form>
4.如何渲染根据左侧选择的option动态渲染右侧select组件
使用select的回调事件,每次选择左侧不同的option,将右侧的值存入state,然后读取这个值生成不同的option
handleNewVariableChange=(optionData,key,value)=>{
/**
* optionData 右边的option 数组
* key 传入的form表单的key 也就是已有变量列表的id或者新增表单的id
* value 左侧的option变化value值 是变量的fromDimensionId
* */
let fromCodeList=[];
optionData.map(dimension=>{
if(dimension.fromDimensionId===value){
fromCodeList=dimension.fromCodeList
}
})
//以key作为state的唯一标志 将属于该key的fromCodeList存入 之后右边的option只需要读这个对应的fromCodeList来生成option
this.setState({
[key]:{"fromCodeList":fromCodeList},
optionChangeFlag:true
})
}
renderSubItem = (initValueObject,taskVariableOptionList,key,getFieldDecorator,initSubValue) =>{
/**
* initValueObject 已有变量列表的id-value对应的对象
* taskVariableOptionList 备选option列表
* key 传入的form表单的key 也就是已有变量列表的id或者新增表单的id
* getFieldDecorator form的方法
* initSubValue 右侧option的默认值
* */
let items=[];
if(!this.state[key]){
//这一步判断左侧option没有变化过 也就是取默认的fromCodeList
if(!initValueObject[key]){
//这一步判断是新增的表单 所有右侧取taskVariableOptionList第一项的fromCodeList来生成option
items=taskVariableOptionList.length>0&&taskVariableOptionList[0].fromCodeList||[];
}else{
//这一步判断是已有的表单列表 右侧要取对应的fromCodeList生成option
let fromDimensionId=initValueObject[key].fromDimensionId;
taskVariableOptionList.map(option=>{
if(option.fromDimensionId===fromDimensionId){
items=option.fromCodeList
}
})
}
}else{
//这一步判断如果左侧option变化了 是取左侧的option发生变化时存入的fromCodeList
items=this.state[key].fromCodeList;
}
if(this.state.optionChangeFlag&&items.length>0){
//如果左侧的option曾经发生变化 那么右侧的initvalue值取第一项的值
initSubValue=items[0].id
}
if(items.length>0){
return(
<div>
{getFieldDecorator(`member#${key}`,{initialValue:initSubValue})(
<Select >
{
items.map(suboption=>{
return (
<Option value={suboption.id} key={suboption.id}>{suboption.name}</Option>
)
})
}
</Select>
)}
</div>
)
}else{
return(
<div>
{getFieldDecorator(`member#${key}`,{initialValue:''})(
<Select initialValue={''}></Select>
)}
</div>
)
}
}