实时搜索与自动补全
场景
实现一个搜索框,用户输入时实时显示搜索建议。需要处理防抖、竞态、关键词高亮等问题。
完整实现
useSearch Hook
import { useState, useEffect, useRef, useCallback } from 'react';
interface UseSearchOptions<T> {
fetchFn: (query: string) => Promise<T[]>;
debounceMs?: number;
minLength?: number;
}
function useSearch<T>({ fetchFn, debounceMs = 300, minLength = 1 }: UseSearchOptions<T>) {
const [query, setQuery] = useState('');
const [results, setResults] = useState<T[]>([]);
const [isLoading, setIsLoading] = useState(false);
const abortRef = useRef<AbortController | null>(null);
useEffect(() => {
if (query.length < minLength) {
setResults([]);
return;
}
// 防抖
const timer = setTimeout(() => {
// 取消上次请求
abortRef.current?.abort();
const controller = new AbortController();
abortRef.current = controller;
setIsLoading(true);
fetchFn(query)
.then((data) => {
if (!controller.signal.aborted) {
setResults(data);
}
})
.catch((e) => {
if (e.name !== 'AbortError') console.error(e);
})
.finally(() => {
if (!controller.signal.aborted) {
setIsLoading(false);
}
});
}, debounceMs);
return () => {
clearTimeout(timer);
abortRef.current?.abort();
};
}, [query, fetchFn, debounceMs, minLength]);
return { query, setQuery, results, isLoading };
}
关键词高亮
function highlightMatch(text: string, query: string): React.ReactNode {
if (!query) return text;
const regex = new RegExp(`(${query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
const parts = text.split(regex);
return parts.map((part, i) =>
regex.test(part) ? <mark key={i}>{part}</mark> : part
);
}
常见面试问题
Q1: 搜索请求的竞态问题怎么解决?
答案:
使用 AbortController。每次发起新请求前取消上一次请求,确保只显示最新查询的结果。在 React 中结合 useEffect 的 cleanup 函数实现。
Q2: 搜索性能优化有哪些手段?
答案:
- 防抖(300ms):减少请求频率
- 最小查询长度:少于 N 个字符不搜索
- 缓存:相同 query 不重复请求
- 取消请求:AbortController 取消过时的请求
- 虚拟列表:结果太多时不全部渲染 DOM