如何在React中用debounce避免CollectionsAutocomplete按键触发GraphQL查询
实现CollectionsAutocomplete的输入防抖逻辑
一、在组件库的CollectionsAutocomplete里加防抖逻辑
1. 准备需要的Hook
要用到useRef存储定时器ID、useCallback封装防抖函数、useState维护防抖后的输入值,先把这些Hook引入组件。
2. 修改组件代码
import { useState, useRef, useCallback, useEffect } from 'react'; type CollectionsAutocompleteProps = GenericFetchAutocompletePropsWrapperType<StyleCollection> & { targetBrandNumber: string; queryVariables?: Exact<{ brandNumber: string; generalQuery?: InputMaybe<string> | undefined; pagination: PaginationInput; }> | undefined; onInputChange?: (inputValue: string) => void; // 可选:允许外部自定义防抖延迟 debounceDelay?: number; }; export const CollectionsAutocomplete = (props: CollectionsAutocompleteProps): ReactElement => { const { queryVariables, targetBrandNumber, value, handleOnBlur, handleOnChange, handleOnFocus, limitTags, onInputChange, debounceDelay = 500 // 默认500ms防抖,可按需调整 } = props; // 实时同步输入框显示的内容 const [inputValue, setInputValue] = useState<string>(''); // 防抖后的输入值,专门用来触发后端请求 const [debouncedInputValue, setDebouncedInputValue] = useState<string>(''); // 存定时器ID,用来清理未执行的防抖任务 const debounceTimerRef = useRef<NodeJS.Timeout | null>(null); // 封装防抖更新函数:延迟更新debouncedInputValue const debounceUpdateInput = useCallback((value: string) => { // 先清掉上一次的定时器,避免重复触发请求 if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } // 设置新定时器,到点再更新防抖值 debounceTimerRef.current = setTimeout(() => { setDebouncedInputValue(value); }, debounceDelay); }, [debounceDelay]); // 组件卸载时清定时器,防止内存泄漏 useEffect(() => { return () => { if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } }; }, []); // 处理失焦:用户离开输入框时立刻触发防抖更新,避免最后一次输入没请求 const handleBlurWithDebounce = useCallback((...args: Parameters<typeof handleOnBlur>) => { handleOnBlur?.(...args); // 如果还有没执行的防抖任务,立刻执行 if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); setDebouncedInputValue(inputValue); } }, [handleOnBlur, inputValue]); return ( <GenericFetchAutocomplete< StyleCollection, GetAllCollectionsQuery, GetAllCollectionsQueryVariables, true> limitTags={limitTags} value={value} dataMapper={(data): Array<StyleCollection> => data.collections?.edges?.map((edge) => edge?.node).filter(sortUtil.isDefined) ?? []} fetchQuery={GetAllCollectionsDocument} inputLabels={{ plural: 'Collections', single: 'Collection', }} queryOptions={{ fetchPolicy: 'network-only', context: { clientName: GraphClientNames['backend'] }, variables: { // 这里换成防抖后的输入值,只有防抖触发后才会请求后端 generalQuery: queryVariables?.generalQuery ?? debouncedInputValue, brandNumber: targetBrandNumber, pagination: { first: 100, after: null, }, ...queryVariables ?? {}, }, }} tagLabelFormatter={(option): string => `${option.collectionTerm} (${option.collectionName})`} textFieldProps={{ ...props.textFieldProps, placeholder: value.length === 0 ? 'Select' : '', sx: { ['& ::placeholder']: { color: '', }, }, }} idKey='collectionNumber' optionLabelFormatter={(option): string => `${option.collectionTerm} (${option.collectionName})`} multiple onBlur={handleBlurWithDebounce} // 替换原onBlur为带防抖触发的版本 onChange={handleOnChange} onFocus={handleOnFocus} onInputChange={(event): void => { const newValue = (event.target as HTMLInputElement).value; // 实时更新输入框显示的内容 setInputValue(newValue); // 触发防抖逻辑 debounceUpdateInput(newValue); onInputChange?.(newValue); }} /> ); };
3. 核心逻辑解释
- 双状态分离:
inputValue保证输入框实时显示用户输入,debouncedInputValue控制后端请求时机,避免按键一次请求一次。 - 定时器管理:每次输入都清掉上一次的定时器,确保只有用户停止输入一段时间后才会发起请求。
- 失焦立即触发:用户输入完直接点其他地方时,立刻触发最后一次请求,不会漏掉输入内容。
- 内存泄漏防护:组件卸载时清掉未执行的定时器,避免残留任务占用资源。
二、项目里的使用方法
在你的项目中使用时,不用改太多代码,如果需要自定义防抖延迟,直接传debounceDelay参数就行:
<CollectionsAutocomplete value={collectionsValue} textFieldProps={{ required: true }} targetBrandNumber={selectedBrands[0]?.brandNumber} queryVariables={{ pagination: { first: 200, after: null, }, }} // 自定义防抖延迟为300ms,按需调整 debounceDelay={300} handleOnChange={(e, v): void => { if (v !== null) { setCollectionsValue(v); } else { setCollectionsValue([]); } }} onInputChange={(newValue: string): void => { console.log('实时输入值:', newValue); }} />
额外提醒
- 如果
GenericFetchAutocomplete是基于Apollo Client或React Query实现的,只要查询变量变化才会发起请求,这里用debouncedInputValue作为查询变量,刚好符合防抖需求。 - 要是不想让空值触发请求,可以在
debounceUpdateInput里加个判断,比如if (value.trim())再更新防抖值。
内容的提问来源于stack exchange,提问作者Jdeveloper
相关产品推荐
相关产品推荐

