需求
在 antd 的 select 多选框中,我们希望增加一个全选的功能,如下:
目前 antd select 组件并没有帮我们实现这样的功能,需要我们自己去实现。
思路分析
接下来我们分析一下这个功能的几个关键点:
第一个需要注意的点其实也就是:全选和取消全选的基本逻辑
- 点击全选 option
- 全选:全选 option 处于选中状态,其他 option 全部变成未选中状态
- 取消全选:所有 option 变成未选中状态
- 点击其他 option
- 如果选中数量等于 option 总数,则全选 option 处于选中状态
- 如果选中数量小于 option 总数,则全选 option 处于未选中状态
第二个需要注意的点是:我们可以完全将全选和取消全选的逻辑封装在组件内部,不让外部感知。
第三个需要注意的点是:注意受控组件与非受控组件的兼容处理。
代码实现
代码比较简单,咱们直接上代码:
import { Select as AntdSelect } from 'antd'
import { Children, useEffect, useRef, useState } from 'react'
const MODE = {
multiple: 'multiple',
tags: 'tags',
single: 'single',
}
const allOptionValue = `all-${Math.random()}`
const Option = AntdSelect.Option
const Select = (props) => {
const {
mode = MODE.single,
allLabel = '全部',
defaultValue = [],
children,
...rest
} = props
const [innerValue, setInnerValue] = useState(defaultValue)
const allOptionsRef = useRef()
useEffect(() => {
const allOptions = new Set()
Children.map(children, (child) => {
if (child.type === Option) {
allOptions.add(child.props.value)
}
})
allOptionsRef.current = [...allOptions]
}, [children?.length])
// 如果不是多选,那就没有全选可言
if (mode !== MODE.multiple && mode !== MODE.tags) {
return <AntdSelect {...props} />
}
const onChange = (currentValue = []) => {
const value = props.value || innerValue
let preIsAll = value?.length > 0 && value?.length === children?.length
let newValue = []
if (preIsAll) {
if (!currentValue.includes(allOptionValue)) {
// 说明是点击 全选option,取消全选
newValue = []
} else {
// 说明是点击 非全选option,取消点击的 option,同时取消全选 option
newValue = currentValue.filter((item) => item !== allOptionValue)
}
} else {
if (currentValue.includes(allOptionValue)) {
// 说明是点击 全选option,全选
newValue = allOptionsRef.current
} else {
newValue = currentValue
}
}
setInnerValue(newValue)
props.onChange?.(newValue)
}
const getValue = () => {
let value = props.value || innerValue // 如果上层没有管理value(受控组件),那就用本层管理value(非受控组件)
return value?.length > 0 && value?.length === children?.length
? allOptionValue
: value
}
return (
<AntdSelect {...rest} mode={mode} value={getValue()} onChange={onChange}>
<Option value={allOptionValue} key={allOptionValue}>
{allLabel}
</Option>
{children}
</AntdSelect>
)
}
Select.Option = Option
export default Select
其实整体的代码还是比较简单的,需要注意的有以下几点:
- 通过捕获 option 可以获取全选的所有内容
useEffect(() => {
const allOptions = new Set()
Children.map(children, (child) => {
if (child.type === Option) {
allOptions.add(child.props.value)
}
})
allOptionsRef.current = [...allOptions]
}, [children?.length])
- 通过 getValue 结合 innerValue 来兼容受控组件和非受控组件的情况
使用
就正常去使用即可,在组件外层并不会感知 all option 选项 的存在。
import Select from './select.jsx'
export default () => {
const onChange = (value) => {
console.log(value)
}
return (
<Select
defaultValue="lucy"
mode="multiple"
style={{ width: 260 }}
onChange={onChange}
>
<Select.Option value="jack">Jack</Select.Option>
<Select.Option value="lucy">Lucy</Select.Option>
<Select.Option value="Yiminghe">yiminghe</Select.Option>
</Select>
)
}