該組件是一個可輸入的下拉選擇組件,支持從預設選項中選擇或手動輸入自定義值。組件基于 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