使用debounce在react函数组件中做防抖

使用debounce在react函数组件中做防抖

背景

在项目中有这样的常见需求:

​ 提供一个搜索输入框,输入框需要响应用户的输入并且不需要用户点击搜索按钮直接发起搜索。

思路
1. 普通实现

其实这样的需求很常见,也并不难,第一反应就是监听用户输入,只要用户有输入操作立即拿到用户输入的数据直接发起搜索查询

  • 优点:响应迅速,并且确实可以达到需求的效果。

  • 缺点:发送多洗无效请求,浪费服务器的性能资源,增加服务器负担。

2. 防抖函数

使用防抖函数(debounce或者lodash.debounce

  • 优点:同样能做出迅速响应,并且能有效去除不必要的请求发送,节约服务器资源性能。

实现:

我手首先是将搜索框封装为一个组件MatSearchInput

import { Icon, InputBaseProps, StandardTextFieldProps, styled, TextField } from "@mui/material";
import { t } from "i18next";
import { memo } from "react";

const SytledInput = styled(TextField)(({ theme }) => {
  return {
    "& .MuiOutlinedInput-root": {
      "& fieldset": {
        borderColor: theme.palette.primary.main,
      },
    },
    "&:hover fieldset": {
      borderColor: theme.palette.primary.main + " !important",
    },
  };
});

interface MatSearchInputProps extends StandardTextFieldProps {
  placeholder?: string;
  // onChange?(): void;
  width?: number;
  inputProps?: InputBaseProps["inputProps"];
}

export default memo(function MatSearchInput(props: MatSearchInputProps) {
  return (
    <SytledInput
      {...props}
      style={{ width: props.width || 250 }}
      InputProps={{
        startAdornment: (
          <Icon sx={{ mr: 1 }} color="primary">
            search
          </Icon>
        ),
      }}
      placeholder={props.placeholder ? t(props.placeholder) : "Search..."}
      color="primary"
      size="small"
    ></SytledInput>
  );
});

然后在父级组件中监听input事件:

  const fetchTemplate = useCallback(() => {
    setLoading(true);
    fetchData(new PageLink(page, pageSize, searchValue, sortProp, "desc")).then((res) => {
      setTemplateData(res);
      setLoading(false);
    });
  }, [page, sortProp, searchValue]);

  useEffect(() => {
    fetchTemplate();
  }, [fetchTemplate]);

...
	<MatSearchInput value={searchValue} onChange={(e) => setSearchValue(e.target.value)} sx={{ ml: 2 }}></MatSearchInput>
...

在这里fetchTemplate为获取数据的方法,使用useCallback进行缓存,也就是说当依赖searchValue发生变化是该方法会重新计算,然后被useEffect监听到并且调用。

然而现在的问题就是输入框组件只要触发onChange事件就会直接引起请求发送,这是我们可以优化的点。

这里就用到了debounce(或者lodash库的debounce方法)

3. 遇到的坑

最开始的实现思路是在输入框组件中用useState维护自己的value和onChange事件,这样用户输入的时候输入框才能正常工作而不会卡顿,然后在自己的onChange事件中触发props传来的onChange,代码如下:

export default memo(function MatSearchInput(props: MatSearchInputProps) {
  const [value, setValue] = useState(props.value);
  const onChange = (event: ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
    emitChangeByProps(event);
  };

  const emitChangeByProps = debounce(function (event: ChangeEvent<HTMLInputElement>) {
    props.onChange(event);
  }, 300);

  return (
    <SytledInput
      {...props}
      style={{ width: props.width || 250 }}
      InputProps={{
        startAdornment: (
          <Icon sx={{ mr: 1 }} color="primary">
            search
          </Icon>
        ),
      }}
      placeholder={props.placeholder ? t(props.placeholder) : "Search..."}
      color="primary"
      size="small"
      value={value}
      onChange={onChange}
    ></SytledInput>
  );
});

运行后没有预想中的效果

原因

思考后发现原因应该是之前说过的性能优化的问题,根据react函数式组件的特性,当useState存储的值value发生变化的时候,组件会重新渲染,因此emitChangeByProps方法没次都是一个新的方法

解决

解决思路就是用useMemo将emitChangeByProps方法缓存起来,这样就可以实现了:

export default memo(function MatSearchInput(props: MatSearchInputProps) {
  const [value, setValue] = useState(props.value);
  const onChange = (event: ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
    emitChangeByProps(event);
  };

  // 这里用debounce做防抖  做性能优化
  const emitChangeByProps = useMemo(() => {
    return debounce(function (event: ChangeEvent<HTMLInputElement>) {
      props.onChange(event);
    }, 300);
  }, [props]);

  return (
    <SytledInput
      {...props}
      style={{ width: props.width || 250 }}
      InputProps={{
        startAdornment: (
          <Icon sx={{ mr: 1 }} color="primary">
            search
          </Icon>
        ),
      }}
      placeholder={props.placeholder ? t(props.placeholder) : "Search..."}
      color="primary"
      size="small"
      value={value}
      onChange={onChange}
    ></SytledInput>
  );
});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值