ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

如何在 React hooks 中防抖

2021-10-19 14:04:07  阅读:179  来源: 互联网

标签:function current const hooks 中防抖 timer React useDebounce fn


防抖(debounce)是前端经常用到的一个工具函数,也是我在面试中必问的一个问题。团队内部推广React hooks以后,我在面试中也加入了相关的题目。如何实现一个useDebounce这个看起来很基础的问题,实际操作起来却让很多背代码的小伙伴漏出马脚。

问题的安排往往是这样的:

  1. 什么是防抖、节流,分别解释一下?
  2. 在白纸上手写一个防抖or节流函数,自己任选(限时4分钟)
  3. react hooks有了解吗?上机实现一个useDebounce、useThrottle
  4. tyepscript有了解吗?用ts再来写一遍
  5. ……

围绕一个主题不断切换考察点,这样一轮下来,轻松又流畅,同时可以试探出很多信息。

实际情况是,很多候选人在第3题就卡住了,不得不说很可惜。

场景还原

写一个防抖函数

一个经典的防抖函数可能是这样的:

function debounce(fn, ms) {
  let timer;
  return function(...args) {
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      fn(...args)
      timer = null;
    }, ms);
  }
}
复制代码

改成react hooks

先提供测试用例:

export default function() {
  const [counter, setCounter] = useState(0);

  const handleClick = useDebounce(function() {
    setCounter(counter + 1)
  }, 1000)

  return <div style={{ padding: 30 }}>
    <Button
      onClick={handleClick}
    >click</Button>
    <div>{counter}</div>
  </div>
}
复制代码

很多小伙伴会想当然的就改成这样:

function useDebounce(fn, time) {
  return debounce(fn, time);
}
复制代码

简单、优雅,还复用了刚才的代码,测试一下,看起来并没有什么问题:

1-7292504.gif

 

但是这个代码如果放上生产环境,你会被用户锤死。

真的吗?

换个用例来试一下:

export default function() {
  const [counter1, setCounter1] = useState(0);
  const [counter2, setCounter2] = useState(0);

  const handleClick = useDebounce(function() {
    console.count('click1')
    setCounter1(counter1 + 1)
  }, 500)

  useEffect(function() {
    const t = setInterval(() => {
      setCounter2(x => x + 1)
    }, 500);
    return clearInterval.bind(undefined, t)
  }, [])


  return <div style={{ padding: 30 }}>
    <Button
      onClick={function() {
        handleClick()
      }}
    >click</Button>
    <div>{counter1}</div>
    <div>{counter2}</div>
  </div>
}

复制代码

2-7292504.gif

 

当引入一个自动累加counter2就开始出问题了。这时很多候选人就开始懵了,有的候选人会尝试分析原因。只有深刻理解react hooks在重渲染时的工作原理才能快速定位到问题(事实上出错不要紧,能够快速定位问题的小伙伴才是我们苦苦寻找的)。

有的候选人开启胡乱调试大法,慌忙修改setCounter1:

const handleClick = useDebounce(function() {
    console.count('click1')
    setCounter1(x => x + 1)
  }, 500)
复制代码

当然结果依然错误,而且暴漏了自己对react hooks特性不够熟悉的问题……

有的候选人猜到是重渲染缓存的问题,于是写成这样:

function useDebounce(fn, delay) {
  return useCallback(debounce(fn, delay), [])
}
复制代码

在配合setCounter1(x => x + 1)修改的情况下,可以得到正确的结果。但并没有正确解决问题。依然是错误的。有兴趣的读者可以复现一下这个现象,思考一下为什么,欢迎留言讨论。

问题出在哪里?

我们在useDebounce里面加个log

function useDebounce(fn, time) {
  console.log('usedebounce')
  return debounce(fn, time);
}
复制代码

3-7292504.gif

 

控制台开始疯狂的输出log。看到这里,很多读者就明白了。如果是前面表现稍好的候选人,我可以提示到此。

每次组件重新渲染,都会执行一遍所有的hooks,这样debounce高阶函数里面的timer就不能起到缓存的作用(每次重渲染都被置空)。timer不可靠,debounce的核心就被破坏了。

如何调整?

修复这个问题可以有很多办法。比如利用React组件的缓存机制:

function useDebounce(fn, delay, dep = []) {
  const { current } = useRef({ fn, timer: null });
  useEffect(function () {
    current.fn = fn;
  }, [fn]);

  return useCallback(function f(...args) {
    if (current.timer) {
      clearTimeout(current.timer);
    }
    current.timer = setTimeout(() => {
      current.fn.call(this, ...args);
    }, delay);
  }, dep)
}
复制代码

就可以实现一个可靠的useDebounce。

同理我们直接给出useThrottle的代码:

function useThrottle(fn, delay, dep = []) {
  const { current } = useRef({ fn, timer: null });
  useEffect(function () {
    current.fn = fn;
  }, [fn]);

  return useCallback(function f(...args) {
    if (!current.timer) {
      current.timer = setTimeout(() => {
        delete current.timer;
      }, delay);
      current.fn.call(this, ...args);
    }
  }, dep);
}
了解更多前端培训相关技术内容欢迎关注小编!

标签:function,current,const,hooks,中防抖,timer,React,useDebounce,fn
来源: https://www.cnblogs.com/majhw/p/15424430.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有