前提:
Formily:整体用SchemaForm,其实表体用Form也可以,只是写法有微小区别,思路是一样的。
数据结构:返回的数据结构大约是这样的:
content:[
{
children:{
children:{
...
}
...
}
...
}
{...}
...
]
实现效果:类似三个Select先选省,再选市,最后选区。在父级选择后,才会加载出子集,且父级发生变化后,子集会作出不同的变化且子集初始选择清空。类似下图
思路和碰壁:
1、数据处理浅谈,主要是用effects生命周期控制,当对应name下的数据发生变化的时候用map对应处理数据。
2、一开始想着使用@formily/antd的Select,后来发现,做出来后当父级发生变化,子集的选定的值不能清空。如果通过effect中的
state.value=null
会有两个不想得到的后果1、组件初始化后如果该字段设置了required:true,那么渲染完成后就会提示没有填入,这不是我们想要的(不管用表单的任何生命周期都是这样)。2、该字段无法选择,一直是空的状态。
3、根据上述思路,如果不定value,用state定义其他属性字段皆不能得到相应的效果。
4、为了改变子集选定的值,尝试过用x-prop、x-component-props传值均不能达到效果,于是目光投到antd,@formily/antd也是从antd在此封装来的,既然封装达不到效果,那么Select组件就需要自己组装了。
计划:
1、用antd的Select自己在此封装出一个能根据数据变化的组件,该组件除了根据传入值做列表渲染之外,当传入数组发生改变的时候清除之前选择的值。在表中复用自己组装的组件。
2、effects仅用于控制数据的拆解和初始化,不负责组件的变化。
3、在第一步中还有个逻辑缺陷,就是如果父级变了,子集没变,依旧不能重新加载。我的处理方法是用effects配合,在该子集的父级上定义变量,如果父级发生变化,会改变变量的值,这个变量同样控制子集是否更新。
条件讲明白了就贴代码解释。
首先贴出封装的Select组件:
import React from 'react'
import { Select } from 'antd'
import _ from 'lodash'
class CreateSelect extends React.Component {
constructor(props) {
super(props)
this.state = {
change_value: null
}
}
handleChange = (e) => {
this.setState({
change_value: e
})
this.props.onChange(e)
}
componentWillUpdate(newProps, newState) {
if (this.props.data !== newProps.data) {
this.handleChange(undefined)
}
}
componentDidMount() {
if (this.props.clear) {
this.handleChange(undefined)
}
}
render() {
return (
<Select
allowClear
style={this.props.set_style}
onChange={this.handleChange}
value={this.state.change_value}
placeholder={this.props.placeholder}
>
{
!_.isEmpty(this.props.data) ?
_.map(this.props.data, item => {
return <Option key={item.value}>{item.label}</Option>
})
: null
}
</Select >
)
}
}
export default CreateSelect
上述代码中:
1、onChange是formily自带的函数,子组件调用函数可以改变父组件中对应字段的value值。
2、用react的componentWillUpdate生命周期控制判断传入值是否变化从而判断value值是否需要改变。
3、在计划的第三点提到的逻辑缺陷的对应的方法就是传入clear变量,但是这个变量不能放在componentWillUpdate这个生命周期和2中的条件并列,会陷入死循环。
我们再看看表单的写法:
<SchemaForm
components={components}
initialValues={initData}
onSubmit={this.onSubmit}
actions={actions}
effects={($, { setFieldState }) => {
$(LifeCycleTypes.ON_FIELD_CHANGE).subscribe(() => {
...
setFieldState('qualification_major', state => {
let temp = [];
if (this.state.choosed_major && (state.value !== this.state.choosed_major)) this.setState({ clear: true })
else this.setState({ clear: false })
const info = this.state.info;
_.map(info, item => {
item.majors.map(item => {
if (item.major_id == state.value) {
item.levels.map(item => {
temp.push({ label: item.level_name, value: item.level_id });
});
}
});
});
this.setState({
level: temp,
choosed_major: state.value
});
});
});
...
>
...
<Field
type="string"
x-props={{ placeholder: '请选择专业' }}
enum={major}
title="专业"
name="major"
x-rules={
{
required: true,
message: "请选择专业"
}
}
x-component="CreateSelect"
x-component-props={{ data: major, placeholder: '请选择专业', set_style: { width: 336 } }}
/>
上述代码中:
1、choosed_major这个变量是判断clear的标准,这个变量缓存上次选择的值,和这一次选择的值做比较。
2、CreateSelect是封装Select组件的名字,且用 x-component-props对该组件传值。在这个props里面并没有设置clear变量,是因为该变量应该放置在他的子集里面,这样的设计,在major字段发生变化的时候clear就为true,无论子集的数据有没有变化都会使得选择的值为空。
这么设计还有个问题:即时报错,虽然通过onChange可以回传值,但是值为空的时候无论设置undefined、null、‘’等等都不能规避这个问题,只能说对比用@formily/antd实现的效果,在使用体验上这种做法更好。
如图所示
这个问题查阅了很多资料暂时没有解决方法,当父集发生变化的时候对应的子集都会报错。Formily在报错时机上面没有提供更多的控制字段。
如有问题,欢迎指正。