跳到主要内容

实时搜索与自动补全

场景

实现一个搜索框,用户输入时实时显示搜索建议。需要处理防抖、竞态、关键词高亮等问题。

完整实现

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

相关链接