antd中,form元素的disabled通用配置
从官方文档中,我们一般比较容易找到的关于form的disabled配置一般有两种,如下:
- 配置于form组件上,对该表单中的全部antd组件生效,大致如下(点击查看官方例子):
<Form disabled={true} >
xxx
</Form>
- 配置于form中某个组件上面,对该组件生效,大致如下:
<Form>
<Form.Item>
<Input disabled={true} />
</Form.Item>
</Form>
定制form中组件的disabled
背景
在平时的开发中,不乏有这样的诉求:根据不同的情况,设置form的对应的一批组件不可编辑。举个例子:
<Form>
<Form.Item name='input1'>
<Input />
</Form.Item>
<Form.Item name='input2'>
<Input />
</Form.Item>
<Form.Item name='input3'>
<Input />
</Form.Item>
<Form.Item name='input4'>
<Input />
</Form.Item>
<Form.Item name='input5'>
<Input />
</Form.Item>
</Form>
例如针对上面的表单,情况1需要设置name为“input1”和“input2”的两个组件不可编辑,情况2又想设置name为“input3”、“input4”和“input5”的三个组件不可编辑。 一般我们会怎么实现呢,可能是下面这样?
// 代码片段1,构造一个state对象用来控制disabled情况
const [disabledMap, setDisabledMap] = useState({});
// 代码片段2,在form使用
<Form>
<Form.Item name='input1'>
<Input disabled={disabledMap.input1}/>
</Form.Item>
<Form.Item name='input2'>
<Input disabled={disabledMap.input2}/>
</Form.Item>
<Form.Item name='input3'>
<Input disabled={disabledMap.input3}/>
</Form.Item>
<Form.Item name='input4'>
<Input disabled={disabledMap.input4}/>
</Form.Item>
<Form.Item name='input5'>
<Input disabled={disabledMap.input5}/>
</Form.Item>
</Form>
// 代码片段3,动态修改disabled
if (情况1) {
setDisabledMap({
input1: true,
input2: true,
input3: false,
input4: false,
input5: false,
})
} else {
setDisabledMap({
input1: false,
input2: false,
input3: true,
input4: true,
input5: true,
})
}
虽然也实现了,但是总感觉写起来麻烦。有没有更好的方式呢? 例如,通过如下配置来实现的话,用起来应该会舒服一些:
<Form disabledMap={{
input1: false,
input2: false,
input3: true,
input4: true,
input5: true,
}}>
</Form>
这样的话,不需要每个组件单独去进行disabled的配置,写起来方便了很多。但是,目前看来,官方没有直接的方案来支持上述这样的实现。
那么,如果如果你对这个实现方案感兴趣,可以继续往下看。如果你有其他的实现方式,欢迎不吝赐教,分享一下。
实现
要想不用在每个组件上面去重复的写disabled,便能实现对其进行是否可编辑的控制,其实仔细想想,官方已经有一个类似的实现,便是本文一开始提及的:配置于form组件上,对该表单中的全部antd组件生效的例子。
这个例子,虽然是一刀切,要么都不可以编辑,要么都可以编辑,没有实现我们要的精确控制。但是,它说明了一个问题,就是antd的组件都实现了通过外部(父组件)来控制组件的是否可编辑的方法。只是现在我们需要自己把方法找出来,然后实现我们的诉求。还有一点,便是此处强调了antd的组件,所以如果是自定义组件,需要额外进行处理,否则,下文提及的实现方案,对该组件无法生效。
我们能想象得到,reactjs提供的原生的,类似这样的能力,无非就是context特性。而熟悉antd的同学也知道,antd支持通过ConfigProvider进行全局配置。那么到了这一步,你能猜到,实现的方案大概是什么样子的了吗。
ConfigProvider支持通过componentDisabled设置 antd 组件禁用状态,而且name是在Form.Item上面设置的,所以我们可以对其进行封装如下:
const useFormItem = ({createConfigProviderProps}): Function => {
const com = (props: {}) => {
const cpExtendProps = createConfigProviderProps?.(props);
return <ConfigProvider {...cpExtendProps}>
<Form.Item {...props}></Form.Item>
</ConfigProvider>;
};
return com;
};
使用的话,大致如下:
const FormItem = useFormItem({
createConfigProviderProps: ({name}: any) => {
if (情况1 && ['input1', 'input2'].includes(name)) {
return {
componentDisabled: true
};
}
if (情况2 && ['input3', 'input4', 'input5'].includes(name)) {
return {
componentDisabled: true
};
}
return {};
}
});
<Form>
<FormItem name='input1'>
<Input />
</FormItem>
<FormItem name='input2'>
<Input />
</FormItem>
<FormItem name='input3'>
<Input />
</FormItem>
<FormItem name='input4'>
<Input />
</FormItem>
<FormItem name='input5'>
<Input />
</FormItem>
</Form>
好的,方案基本上就是这样了,当然,相信你已经get到,这不仅仅可以用于disabled的配置。
拓展
回到本文一开始的时候提及的,antd既可以在form上面进行全局的disabled设置,又可以在组件上面进行设置,那么如果两个同时进行了设置的话,哪个优先级高呢。例如:
// 这种情况下,input到底可不可以编辑呢?
<Form disabled={true}>
<Form.Item>
<Input disabled={false} />
</Form.Item>
</Form>
结果可能和你想象中的不太一样,看完下面这段代码,你也许就懂了
// antd 4.23.0
const Input = forwardRef<InputRef, InputProps>((props, ref) => {
const {
disabled: customDisabled,
...rest
} = props;
const disabled = React.useContext(DisabledContext);
const mergedDisabled = customDisabled || disabled;
<RcInput disabled={mergedDisabled || undefined} />
}
从源码来看,本意应该是设置在组件上面的disabled要高于form上的。但是这种写法导致了,当组件上面disabled设置为false的时候,会以form上面的为准(看起来像个bug)。