yaofly2012 / note

Personal blog

Home Page:https://github.com/yaofly2012/note/issues

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

函数组件里如何正确使用防抖(_.debounce)函数

yaofly2012 opened this issue · comments

commented

背景

假设有个Search组件,用户在输入过程中,需要异步调用接口获取用户搜索的结果。为了更有效的调用接口我们会使用防抖函数处理接口调用频率。

实现(V1)

import { useEffect, useState } from "react";
import { debounce } from "lodash";

export default function App() {
  const [value, setValue] = useState("");

  const handleChange = (e) => {
    setValue(e.target.value);
  };

  const logger = debounce(value => {
    setTimeout(() => {
      console.log(`Letter Count ${(value || "").length}`);
    }, 300);
  }, 240);

  useEffect(() => {
    logger(value);
  }, [value]);

  return (
    <div className="App">
      <p>输入过程中查看console输出</p>
      <input onChange={handleChange} value={value} />
    </div>
  );
}

效果&问题

1

在输入过程中,好像接口调用只是被delay了(类似setTimeout效果),并没有防抖效果。

原因分析

每次render是都是创建一个新的debounced函数,并没有启到防抖效果。更像是执行一个setTimeout延时调用接口,调用接口次数并没有减少。

实现(V2)

import { useEffect, useState, useRef } from "react";
import { debounce } from "lodash";

export default function App() {
  const [value, setValue] = useState("");

  const handleChange = (e) => {
    setValue(e.target.value);
  };

  const loggerRef = useRef(
    debounce((value) => {
      setTimeout(() => {
        console.log(`Letter Count ${(value || "").length}`);
      }, 300);
    }, 240)
  );

  useEffect(() => {
    loggerRef.current(value);
  }, [value]);

  return (
    <div className="App">
      <p>输入过程中查看console输出</p>
      <input onChange={handleChange} value={value} />
    </div>
  );
}

效果&问题

2

利用useRef保存debounced函数,这样就启到防抖效果了。但是这样写还是存在一些问题:

  1. 每次render时,虽然loggerRef.current的值不变,但是还是创建了debounced`函数;
  2. useEffect的清理函数里把被延时执行的操作cancel掉,可以在组件卸载是执行下cancel操作。

改进实现

可以自定义个hook:useDebounce

/**
 * 自定义hooks专门处理
 */
function useDebounce(func, wait, options) {
  const debounced = useRef();
  // “懒”计算
  if (debounced.current === void 0) {
    debounced.current = debounce(func, wait, options);
  }

  useEffect(() => {
    return () => {
      debounced.current.cancel(); // 组件卸载时执行下`cancel`
    };
  }, []);

  return debounced;
}

引用useDebounce

import { useEffect, useState, useRef } from "react";
import { debounce } from "lodash";

export default function App() {
  const [value, setValue] = useState("");

  const handleChange = (e) => {
    setValue(e.target.value);
  };

  const loggerRef = useDebounce((value) => {
    setTimeout(() => {
      console.log(`Letter Count ${(value || "").length}`);
    }, 300);
  }, 220);

  useEffect(() => {
    const debounced = loggerRef.current;
    debounced(value);

    // return () => debounced.cancel(); // 这里没有必要调用`cance`操作,debounced内部会清理delay的setTimeout
  }, [value]);

  return (
    <div className="App">
      <p>输入过程中查看console输出</p>
      <input onChange={handleChange} value={value} />
    </div>
  );
}