该组件是一个可输入的下拉选择组件,支持从预设选项中选择或手动输入自定义值。组件基于 React
和 Ant Design
实现,具有良好的交互体验和灵活的配置选项。
🧠 核心逻辑分析
1. 状态管理
const [isInput, setIsInput] = useState(false);
const selectRef = useRef();
const inputFlagRef = useRef();
isInput
: 控制当前是否处于自定义输入模式。- selectRef用于引用
Select
或Input
组件,控制焦点。 - inputFlagRef 用于延迟触发
onBlur
事件,避免误操作。
2. 生命周期控制
useEffect(() => {selectRef.current && selectRef.current?.focus();return () => {if (inputFlagRef.current) {clearTimeout(inputFlagRef.current);}inputFlagRef.current = null;};
}, []);
- 组件首次挂载时自动聚焦。
- 组件卸载时清除定时器,防止内存泄漏。
useEffect(() => {if (isInput) {selectRef.current && selectRef.current?.focus();}
}, [isInput]);
- 当切换到自定义输入模式时,自动聚焦到输入框。
3. 值变更处理
const triggerChange = (val) => {onChange(val);
};
- 封装
onChange
,用于统一处理值变更逻辑。
4. 下拉菜单渲染
const renderDropdownContent = (menu) => {return (<>{menu}<Divider style={{ margin: '8px 0' }} /><div style={{ width: '100%' }}><Buttontype="text"style={{ width: '100%' }}onClick={() => {if (inputFlagRef.current) {clearTimeout(inputFlagRef.current);}inputFlagRef.current = null;setIsInput(true);}}>{defaultTitle}</Button></div></>);
};
- 在下拉菜单底部添加一个按钮,点击后切换为自定义输入模式。
- 清除之前的定时器,防止误触发
onBlur
。
🧱 组件结构详解
自定义输入模式 (isInput === true
)
<Inputref={selectRef}placeholder="请输入"{...rest}maxLength={maxLength || 50}value={value}onChange={(ev) => {triggerChange(ev.target.value ?? '');}}onFocus={() => {triggerChange('');}}onBlur={() => {setIsInput(false);rest.onBlur && rest.onBlur();}}
/>
- 展示
Input
输入框。 - onFocus 清空当前值,提供更好的输入体验。
- onChange 实时更新值。
onBlur
: 失焦后切换回下拉选择模式,并触发外部onBlur
回调。
下拉选择模式 (isInput === false
)
<Selectstyle={{ width: '100%' }}ref={selectRef}{...rest}placeholder="请选择"dropdownRender={renderDropdownContent}{...omit(rest, ['onPressEnter'])}mode="multiple"value={value ? value.split(',') : []}onChange={(val) => {triggerChange(val ? val.join(',') : val);rest.onBlur && rest.onBlur();}}onBlur={() => {inputFlagRef.current = setTimeout(() => {rest.onBlur && rest.onBlur();}, 361);}}
>{options.map((item) => (<Option key={item.value}>{item.label}</Option>))}
</Select>
- 使用
mode="multiple"
支持多选,返回值为逗号拼接的字符串。 - value 将字符串值拆分为数组传入
Select
。 - onChange将选中的数组值拼接为字符串返回。
onBlur
: 延迟触发外部onBlur
回调,避免误操作。
🧪 使用示例
import React, { useRef } from 'react';
import SelectInput from '@/biz-components/SelectInput';const Demo = () => {const [value, setValue] = React.useState('');const selectInputRef = useRef();const options = [{ label: '选项1', value: '1' },{ label: '选项2', value: '2' },{ label: '选项3', value: '3' },];return (<SelectInputref={selectInputRef}value={value}onChange={setValue}options={options}placeholder="请选择或输入"/>);
};
全部代码
import React, { useState, useRef, useEffect } from 'react';
import { Select, Divider, Button, Input } from 'antd';
import { omit } from 'lodash';const { Option } = Select;// eslint-disable-next-line no-unused-vars
const CustomSelectSupportInput = (props, ref) => {const { value, onChange, options = [], defaultTitle = '自定义', maxLength, ...rest } = props;const [isInput, setIsInput] = useState(false);const selectRef = useRef();const inputFlagRef = useRef();useEffect(() => {selectRef.current && selectRef.current?.focus();return () => {if (inputFlagRef.current) {clearTimeout(inputFlagRef.current);}inputFlagRef.current = null;};}, []);useEffect(() => {if (isInput) {selectRef.current && selectRef.current?.focus();}}, [isInput]);const triggerChange = (val) => {onChange(val);};// 下拉项const renderDropdownContent = (menu) => {return (<>{menu}<Divider style={{ margin: '8px 0' }} /><div style={{ width: '100%' }}><Buttontype="text"style={{ width: '100%' }}// onMouseDown={() => {onClick={() => {// inputFlagRef.current = true;if (inputFlagRef.current) {clearTimeout(inputFlagRef.current);}inputFlagRef.current = null;setIsInput(true);}}>{defaultTitle}</Button></div></>);};return (<>{isInput && (<Inputref={selectRef}placeholder="请输入"{...rest}maxLength={maxLength || 50}value={value}onChange={(ev) => {triggerChange(ev.target.value ?? '');}}onFocus={() => {triggerChange('');}}onBlur={() => {setIsInput(false);rest.onBlur && rest.onBlur();}}/>)}{!isInput && (<Selectstyle={{ width: '100%' }}ref={selectRef}{...rest}placeholder="请选择"dropdownRender={renderDropdownContent}{...omit(rest, ['onPressEnter'])}mode="multiple"value={value ? value.split(',') : []}onChange={(val) => {triggerChange(val ? val.join(',') : val);// 变更后主动失焦保存数据,避免直接点击外部的 ‘添加’ 按钮触发失焦,导致行更新数据丢失rest.onBlur && rest.onBlur();}}onBlur={() => {// if (inputFlagRef.current) {// return;// }inputFlagRef.current = setTimeout(() => {rest.onBlur && rest.onBlur();}, 361);}}>{options.map((item) => (<Option key={item.value}>{item.label}</Option>))}</Select>)}</>);
};export default React.forwardRef(CustomSelectSupportInput);
🧩 扩展建议
- 可通过
dropdownRender
自定义下拉菜单内容。 - 可结合
Form.Item
使用,支持表单校验。 - 可扩展支持远程搜索、自动补全等功能。
- 可增加
onSearch
回调支持动态搜索选项。
📚 参考文档
- React
- Ant Design - Select
- Lodash - omit