记录一个react+antd中使用数组循环出checkboxgroup的过程

前段时间项目中遇到一个需求,效果如下图:
在这里插入图片描述
这个红框里面的是通过一个数组渲染出来的,后端给过来的数据格式是一个一维数组:

[{
	title: '意图',
	label: '搭讪',
	value: '1'
}, {
	title: '方向',
	label: '出',
	value: '3’
}, {
	title: '意图',
	label: '搭讪',
	value: '1'
}]

前端需要将数组通过title来升级为二维数组,这样才能map渲染出上面要的效果。不同的title为不同的分组。这个升级的的方法我是这样写得:

const createClssifyOption = (arr) => {
  let result = [];
  let map = new Map();
  for (let i = 0; i < arr.length; i++) {
    if (map.has(arr[i].category)) {
      result[result.length - 1].options.push({
        label: arr[i].labelName,
        value: arr[i].labelId.toString(),
      });
    } else {
      map.set(arr[i].category, 1);
      result.push({
        title: arr[i].category,
        defaultValue: [],
        options: [
          {
            label: arr[i].labelName,
            value: arr[i].labelId.toString(),
          },
        ],
      });
    }
    if (arr[i].checked) {
      result[result.length - 1].defaultValue.push(arr[i].labelId.toString());
    }
  }
  return result;
};

转换之后就拿到了我想要的数据格式,如下:
在这里插入图片描述
注意:defaultValue是为了配合antd的checkboxgroup的API,不清楚可以看官方文档。
通过这个数组渲染出页面没有问题。

设定defaultValue后问题来了:
每次翻页页面显示的都是上一次请求过来的数据,而断点打印显示setState也生效了。
表示很无语,,,但是但是,我用了value之后就可以实现实时显示了,目测是antd的bug。
接下来又有问题了,checkbox的事件如何绑定呢?map出来的只能写一个方法来处理。
苦思冥想之后,方法如下:

结合js中对象更改内存地址不会变的原理,直接更改当前item中的defaultValue

//  v是每次选中的时候当前checkboxgroup的选中值的集合
// i是当前的checkboxgroup的索引
const chooseClassify = (v, i) => {
      this.setState({
        classifyArr: this.state.classifyArr.map((item, index) => {
          console.log(index, i);
          if (index === i) {
            return {
              ...item,
              defaultValue: v,
            };
          }
          return item;
        }),
      });
    };

然后每次保存的时候,将渲染数组中的defaultValue push到一个数组中传给后端就ok了。

问题搞定,,,晚饭加鸡腿!!

<think>我们参考引用[1]和[2]中提供的思路:使用Checkbox.Group的onChange事件,通过状态管理实现单选。 核心逻辑:在onChange事件中,我们获取当前选中的值数组(index)。由于是单选,我们只保留最后一次操作的值。但是注意,用户可能取消选中,所以需要判断: - 如果当前操作导致选中值数组为空(即取消选中),则保留空数组。 - 否则,我们取当前操作新增的值(即当前选中而之前未选中的值)作为新的选中值。 然而,引用[1]和[2]中的实现方式是通过比较上一次的选中数组和当前选中的数组,找新增的值,然后只保留这个新增的值(即单选效果)。具体步骤: 1. 在state中维护一个数组(例如chkSelectIndex)表示当前选中的值(虽然单选,但Checkbox.Group的值是数组形式)。 2. 在onChange中,得到当前选中的数组index(即所有被选中的值)。 3. 比较当前选中的数组index和之前的状态chkSelectIndex,找新增的值(即index中有而chkSelectIndex中没有的值)。 4. 如果找到了新增的值,则设置状态为这个新增的值(即单选,只保留这个值);如果没有新增(即取消操作),则设置状态为空数组。 但是,我们也可以简化:因为单选要求只能选一个,所以我们可以认为用户每次点击都是切换选中状态。那么: - 如果用户点击一个未选中的项,则应该选中该项,并取消其他所有项。 - 如果用户点击一个已选中的项,则取消选中(变为空)。 因此,我们可以这样处理: - 如果当前选中的数组index的长度为0,说明用户取消了所有选中,那么我们就设置状态为空数组。 - 如果当前选中的数组index的长度大于0,那么我们需要找这次操作中新增的值(即当前选中而之前未选中的值),这个值就是用户最新点击的值。然后我们将状态设置为只包含这个值的数组。 但是注意:有可能用户同时取消多个?但单选情况下,每次操作只可能改变一个选项。所以我们可以这样: - 由于是单选,我们只关心用户最新操作的那个选项。在Checkbox.Group的onChange中,我们无法直接知道是哪个选项被点击了,但我们可以通过比较上一次选中的数组和当前选中的数组来得到变化的项。 引用[1]和[2]中的实现: const tmpArr = index?.filter(item => { return selectData.indexOf(item) === -1; }); setChkSelectIndex(tmpArr); 这里,tmpArr就是新增的项(因为只有新增的项才会在index中有,而在之前的selectData中没有)。如果tmpArr为空,说明用户是取消操作(因为index比selectData少,但这里逻辑是:如果用户取消,那么index会少一个,那么filter就会得到空数组,所以状态变成空数组,即取消选中)。如果tmpArr不为空,那么就是新增的项,我们就把状态设置为这个新增的项(数组形式,但只有一个元素)。 但是,这种逻辑有一个问题:如果用户先选中A(此时状态为A),再选中B,那么: 第一次选中A:index=[A],selectData=[] -> tmpArr=[A] -> 状态设为[A] 第二次选中B:index=[A,B](因为之前已经选中了A,现在又选中B,所以是[A,B]),selectData=[A] -> tmpArr=B(因为B不在selectData中) -> 状态设为[B] 这样就实现了单选。 但是,如果用户先选中A,再点击A(取消A),那么: 第一次:状态[A] 第二次:index=[](因为用户取消了A,所以没有选中的了),那么进入else分支?不,在代码中,当index.length为0时,会执行if(index.length===0)分支,直接设置状态为selectData(即原来的[A])?不对,在引用[1]和[2]的代码中,当index.length为0时,执行的是setChkSelectIndex(selectData)?这会导致状态还是[A]?这显然不对。 我们看引用[1]和[2]的代码: if (index.length === 0) { setChkSelectIndex(selectData); // 这里selectData是上一次的状态,即[A],所以取消操作后状态还是[A]?这就不对了。 所以,我们需要修改:当用户取消时,应该设置状态为空数组。但是,在引用[1]和[2]的代码中,取消操作(index.length为0)时,他们设置的是上一次的状态(selectData),这会导致无法取消。 因此,我们需要调整逻辑: - 当index.length为0时,表示用户取消了所有选中(即最后一个选中的也被取消),那么我们应该设置状态为空数组。 - 否则,我们取新增的项(即tmpArr),如果tmpArr有值,则设置为tmpArr(即最新选中的项);如果没有新增项(即用户取消了一个,但此时index.length不为0,说明还有其他选中项?但单选情况下不会现这种情况,因为单选只能有一个选中,所以取消一个后就没有了,所以不会进入这个分支?) 但是,在单选场景下,我们每次操作只能改变一个选项,所以当用户取消时,index.length应该等于chkSelectIndex.length-1(因为取消了一个),但是我们的逻辑是:当用户取消时,index数组就是上一次的数组去掉取消的那一项。所以index.length一定大于0?除非原来只有一个,取消后就没有了,所以index.length=0。 因此,正确的逻辑应该是: - 如果当前选中的数组index的长度为0,说明用户取消了唯一选中的那个,那么设置状态为空数组。 - 否则,说明用户进行了选中操作(可能是新增一个,但单选情况下,新增一个的同时需要取消之前选中的那个,所以这里我们只取新增的那个)。注意:在Checkbox.Group中,当我们选中一个新的,它会自动加上,而不会自动取消之前选中的,所以我们需要通过设置状态来覆盖。 所以,我们修改引用[1]和[2]的代码: if (index.length === 0) { setChkSelectIndex([]); // 取消所有,设置空数组 } else { // 找新增的项(可能有多个?但单选操作每次只能新增一个,因为我们之前已经覆盖了多选的情况,所以这里取最后一个新增的?或者取第一个?) // 实际上,由于用户只可能点击一个,所以新增的项最多一个。我们取第一个即可。 const newItem = index.find(item => !chkSelectIndex.includes(item)); if (newItem) { setChkSelectIndex([newItem]); // 设置为新选中的项 } else { // 这种情况一般不会现,因为如果没有新增项,说明用户是取消操作,但index.length>0,说明还有选中的,但用户取消了一个,那么剩下的就是之前选中的(没有变化),所以不需要更新? // 但是,如果用户先选中A,然后又选中A(取消),那么会进入index.length=0的分支,所以这里不会进入。 } } 但是,这样写比较繁琐。实际上,我们还有一种更简单的思路:利用Checkbox.Group的value属性,我们只允许一个值被选中。那么当用户点击时,我们直接判断: - 如果点击的选项已经是选中的,那么我们就取消它(设置空数组)。 - 如果点击的选项是未选中的,那么我们就选中它(设置为该选项的值),并取消之前选中的。 但是,在Checkbox.Group中,我们无法直接知道用户点击了哪个选项(除非我们给每个Checkbox单独设置onChange,但这样就不能用Checkbox.Group了)。 因此,我们回到引用[1]和[2]的方法,并修正取消的问题: const handleChange = (index) => { // 当前选中的数组index if (index.length === 0) { setChkSelectIndex([]); return; } // 找新增的项(即当前index有而之前chkSelectIndex没有的项) const tmpArr = index.filter(item => !chkSelectIndex.includes(item)); // 如果tmpArr有值,说明用户新增了一个选项(单选,所以只取这个新增的) if (tmpArr.length > 0) { setChkSelectIndex(tmpArr); // 这里tmpArr是一个数组,但只有一个元素(因为每次操作只可能新增一个,因为之前是单选,最多一个选中,现在新增一个,最多一个) } else { // 如果没有新增项,说明用户是取消操作(因为index.length>0,但chkSelectIndex中有的项index中可能没有?不,index是当前选中的所有项,而chkSelectIndex是上一次的,如果用户取消了一个,那么index应该比chkSelectIndex少一个,所以index中的每一项都在chkSelectIndex中,所以tmpArr为空。那么此时,我们怎么知道用户取消了哪个?我们不需要知道,因为单选情况下,用户取消后,剩下的就是之前选中的(但此时index.length>0,所以还有选中的项),但我们希望的是:用户取消操作不会影响其他项?不对,单选情况下,用户只能取消当前选中的项,而不会影响其他项(因为其他项本来就没选中)。所以这里其实不需要更新状态?因为状态还是之前的状态(除了被取消的那项,其他没变)。但是,注意:我们之前的状态是只有一个选中的,现在用户取消了这个选中的,那么index数组应该是空数组,但这里index.length>0,所以不会进入这个分支?这不对。 // 实际上,这种情况不会发生。因为当用户取消当前选中的唯一项时,index.length=0,已经处理了。所以这里else分支的情况不会现。 // 所以我们可以省略else分支,或者保留但不做处理。 } } 但是,我们再看一个场景:假设当前选中A,用户点击B,那么index=[A,B](因为Checkbox.Group是多选,所以会同时选中A和B),然后我们通过tmpArr得到B(因为A已经在chkSelectIndex中,所以B是新增的),然后设置状态为[B],这样就实现了单选。然后用户再点击B(取消B),那么index=[],进入第一个分支,设置为[]。 但是,如果用户同时点击两个(比如快速点击)?这不可能,因为每次点击都会触发onChange,所以一次只改变一个。 因此,我们采用引用[1]和[2]的方法,并修正取消操作为空数组: if (index.length === 0) { setChkSelectIndex([]); } else { const tmpArr = index.filter(item => !chkSelectIndex.includes(item)); setChkSelectIndex(tmpArr); } 这样,当用户取消时(index.length=0),状态置为空数组;当用户选中一个时,tmpArr就是新增的那个选项(数组),然后设置为该数组(单选)。 但是,这里有一个问题:当用户点击一个已经选中的项(即当前状态是[value],用户再次点击该value),那么index会变成空数组吗?不会,因为Checkbox.Group的onChange中,当用户点击一个已选中的项,会取消该项,所以index数组会变成空数组(因为之前只有一个,取消后就没有了),所以会进入第一个分支,设置为空数组。 而当用户点击一个未选中的项,index数组会包含之前选中的项和当前点击的项(因为Checkbox.Group默认是多选,所以会保留之前选中的项),然后我们通过filter找新增的项(即当前点击的项),然后设置状态为[当前点击的项]。这样,下一次渲染时,Checkbox.Group的value就是[当前点击的项],所以之前选中的项就会自动取消(因为我们只传了一个值)。 因此,这个逻辑是可行的。 但是,我们需要注意:在初始状态时,如果默认选中一个(比如defaultValue=[1]),那么当用户点击另一个时,index数组会包含默认选中的1和当前点击的值,然后我们通过filter得到当前点击的值(因为1已经在之前的chkSelectIndex中,所以被过滤掉),然后设置状态为[当前点击的值],这样就实现了切换。 所以,我们按照这个思路实现。 另外,我们也可以使用另一种更简单的方法:直接记录最后一次选中的值,然后每次点击时,如果点击的值和当前选中的值相同,就取消选中(置空),否则就选中新的值。但是,这种方法需要单独处理每个Checkbox,不能使用Checkbox.Group,所以我们不采用。 因此,我们使用Checkbox.Group,并采用上述onChange逻辑。 代码实现如下: 注意:我们使用useState来管理选中的值(数组形式)。 另外,如果我们需要在表单中使用,那么可以结合Form.Item的name,通过form.setFieldsValue来设置,但思路相同。 下面是一个完整的示例:</think>在 React使用 Ant Design 的 `Checkbox.Group` 组件实现单选功能,需要通过状态管理和自定义 `onChange` 事件来控制选中逻辑。以下是完整实现方案: ### 实现方法 ```jsx import React, { useState } from 'react'; import { Checkbox } from 'antd'; const RadioCheckboxGroup = () => { // 选项配置 const options = [ { label: '选项 A', value: 'A' }, { label: '选项 B', value: 'B' }, { label: '选项 C', value: 'C' }, ]; // 状态管理当前选中值(数组形式) const [selectedValues, setSelectedValues] = useState([]); // 处理单选逻辑 const handleChange = (checkedValues) => { // 获取新增的选项(当前选中但之前未选中的值) const newSelections = checkedValues.filter( value => !selectedValues.includes(value) ); // 单选逻辑:保留最新选中的值 setSelectedValues(newSelections.length > 0 ? [newSelections[newSelections.length - 1]] : [] ); }; return ( <Checkbox.Group options={options} value={selectedValues} onChange={handleChange} /> ); }; export default RadioCheckboxGroup; ``` ### 关键点说明 1. **单选逻辑核心**: - 通过 `newSelections` 获取当前操作新增的选项 - 使用 `[newSelections[newSelections.length - 1]]` 保留最新选中的值 - 数学表达:设选中状态为 $x_i \in \{0,1\}$,则单选约束为 $\sum_{i=1}^{n} x_i \leq 1$ 2. **状态管理**: - `selectedValues` 始终为包含0或1个元素的数组 - 当取消选中时设为空数组 `[]` 3. **与表单集成**(结合Ant Design Form): ```jsx // 在Ant Design Form中使用 <Form.Item name="radioOption" label="单选选项"> <Checkbox.Group options={options} value={selectedValues} onChange={handleChange} /> </Form.Item> ``` ### 完整表单示例 ```jsx import React, { useState } from 'react'; import { Form, Checkbox, Button } from 'antd'; const FormWithRadioCheckbox = () => { const [form] = Form.useForm(); const [selectedValue, setSelectedValue] = useState([]); const options = [ { label: 'Apple', value: 'apple' }, { label: 'Orange', value: 'orange' }, { label: 'Banana', value: 'banana' }, ]; const handleChange = (checkedValues) => { const newSelections = checkedValues.filter(v => !selectedValue.includes(v)); const newValue = newSelections.length > 0 ? [newSelections[newSelections.length - 1]] : []; setSelectedValue(newValue); form.setFieldsValue({ fruit: newValue.length > 0 ? newValue[0] : null }); }; const onFinish = (values) => { console.log('提交值:', values.fruit); }; return ( <Form form={form} onFinish={onFinish}> <Form.Item name="fruit" label="选择水果" rules={[{ required: true, message: '请至少选择一个选项' }]} > <Checkbox.Group options={options} value={selectedValue} onChange={handleChange} /> </Form.Item> <Button type="primary" htmlType="submit">提交</Button> </Form> ); }; ``` ### 注意事项 1. **禁用未选中项**(增强用户体验): ```jsx options.map(option => ( <Checkbox key={option.value} value={option.value} disabled={selectedValue.length > 0 && !selectedValue.includes(option.value)} > {option.label} </Checkbox> )) ``` 2. **性能优化**: - 对于大量选项,使用 `options` 数组配置代替JSX循环 - 使用 `React.memo` 避免不必要的重渲染 3. **默认值设置**: ```jsx // 初始化默认选中 const [selectedValue, setSelectedValue] = useState(['orange']); ``` 此实现方案通过状态控制模拟单选行为,同时保持与Ant Design Form的兼容性,确保表单提交时能正确获取单选值[^1][^2]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值