React SearchBar组件下拉项onClick失效:focus状态控制显示的问题
问题原因
核心问题是input的onBlur事件触发时机早于下拉项的onClick:当你点击下拉列表的<li>时,浏览器会先触发input的blur事件,把searchFocus设为false,这时候下拉面板会被立即卸载,<li>的点击事件根本没机会执行。
解决方案
下面提供几种可行的修复方式,按推荐程度排序:
方法1:监听全局点击,判断点击区域(最健壮)
通过ref标记组件容器,监听全局点击事件,只有点击组件外部时才关闭下拉面板:
type Props = { onClickResult: (result: string, key: number) => void placeHolder: string results: string[] holdValue: boolean } const SearchBar = (props: Props): JSX.Element => { const [search, setSearch] = useState<string>('') const [searchFocus, setSearchFocus] = useState<boolean>(false) // 创建ref标记组件容器 const searchComponentRef = useRef<HTMLDivElement>(null); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { // 点击区域不在组件内时,关闭面板 if (searchComponentRef.current && !searchComponentRef.current.contains(event.target as Node)) { setSearchFocus(false); } }; // 添加全局监听 document.addEventListener('mousedown', handleClickOutside); // 组件卸载时移除监听 return () => document.removeEventListener('mousedown', handleClickOutside); }, []); function handleChooseResult(result: string, key: number) { props.onClickResult(result, key) props.holdValue ? setSearch(result) : setSearch('') } return ( <div id="search-component" ref={searchComponentRef}> <div className="search-bar"> <FaSearch id="search-icon"/> <input type="text" placeholder={props.placeHolder} value={search} onChange={(e) => setSearch(e.target.value)} onFocus={() => setSearchFocus(true)} // 移除原来的onBlur /> </div> <div className="search-results"> { searchFocus && <ul> {props.results.filter((result) => // 优化过滤逻辑,避免substring的长度问题 result.toLowerCase().startsWith(search.toLowerCase()) ).map((result, key) => <li key={key} onClick={() => handleChooseResult(result, key)}>{result}</li> )} </ul> } </div> </div> ) }
方法2:用onMouseDown替代onClick
因为mousedown事件会在blur之前触发,所以能保证点击逻辑先执行:
// 只修改<li>的事件 <li key={key} onMouseDown={() => handleChooseResult(result, key)}>{result}</li>
注意:如果需要阻止input失去焦点,可以在handleChooseResult里添加event.preventDefault(),但一般不需要,选择后关闭面板是合理的。
方法3:延迟onBlur的状态更新
给onBlur加一个短延迟,让onClick有足够时间执行:
<input type="text" placeholder={props.placeHolder} value={search} onChange={(e) => setSearch(e.target.value)} onFocus={() => setSearchFocus(true)} onBlur={() => setTimeout(() => setSearchFocus(false), 100)} />
这个方法简单但不够优雅,延迟时间需要调整,极端情况下可能还是会出问题。
额外代码优化建议
- 不要把props存到本地state:你当前用
useState存了props.results,但父组件更新results时,组件内的results不会同步更新,直接用props.results即可。 - 优化过滤逻辑:原来的
substring写法在search长度大于result时会报错,改成startsWith更安全,也符合前缀匹配的搜索场景;如果需要模糊匹配,用includes即可。
内容的提问来源于stack exchange,提问作者Caue
相关产品推荐
相关产品推荐

