You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 08:25:55