碎碎念: 今天公司新入職一位人事小姐姐,打破了我在公司的記錄——「公司最小的員工」。絕對(duì)想不到她多小——零二年的??????,這的多聰明,上學(xué)連跳好幾級(jí)了吧,佩服??,不過(guò)還好我們倆算是同齡人 ??????。
前言:有些東西就應(yīng)該大膽的去嘗試,嘗試之后,你就會(huì)發(fā)現(xiàn),哇咔咔好多坑,emmmm,摸著石頭過(guò)河,才發(fā)現(xiàn)河底都是石頭 ??????,就比如現(xiàn)在用 React hooks ,寫(xiě)著寫(xiě)著,遇見(jiàn)難題了,不知道去哪防抖了,函數(shù)使用 useCallback 做緩存,每次依賴(lài)一更新函數(shù)都會(huì)被重建,導(dǎo)致平時(shí)用的 debounce 函數(shù)毛線現(xiàn)在不能用了。
讓我們先從簡(jiǎn)單的加法器開(kāi)始
一、簡(jiǎn)單的加法器
使用 React hooks 的 useState 寫(xiě)一個(gè)簡(jiǎn)單的加法器,效果如下:

源代碼:
import React, { useState } from "react";
export default () => {
const [ count, setCount ] = useState(0);
const handleClick = () => {
setCount(count + 1);
};
return (
<>
<h3> 計(jì)算結(jié)果: {count} </h3>
<button onClick={handleClick}>每次加一</button>
</>
);
};
handleClick 是一個(gè)函數(shù),一般來(lái)講我們?yōu)榱撕瘮?shù)緩存會(huì)使用 useCallback,即避免無(wú)關(guān)父組件 props更新和不是 count 引發(fā)的子組件更新,變更如下:
const handleClick = useCallback(() => {
console.log(count);
setCount(count + 1);
}, [ count ]);
好了,難題來(lái)了,我們先做一個(gè)快速點(diǎn)擊按鈕??:
import React, { useCallback } from "react";
import debounce from "lodash.debounce";
export default () => {
const handleClick = useCallback(debounce(() => console.log("click fast!"), 1000), [ ]);
return (
<>
<button onClick={handleClick}>click fast!</button>
</>
);
};
當(dāng) useCallback 無(wú)依賴(lài)項(xiàng)時(shí),函數(shù)一旦被創(chuàng)建就不會(huì)重載了,這個(gè)地方可以放心使用 debounce。
不信??!,我們來(lái)看看我們的加法器加上 debounce 的效果,修改如下:
import React, { useState, useCallback } from "react";
import debounce from "lodash.debounce";
export default () => {
const [ count, setCount ] = useState(0);
const handleClick = useCallback(debounce(() => setCount(count + 1), 1000, { leading: false, trailing: true }), [ count ]);
return (
<>
<h3> 計(jì)算結(jié)果: {count} </h3>
<button onClick={handleClick}>每次加一</button>
</>
);
};
{ leading: false, trailing: true } 這個(gè)是 lodash.debounce 函數(shù)的默認(rèn)值,表示防抖時(shí),最后一次按鈕觸發(fā)執(zhí)行函數(shù)。好了,當(dāng)你狂點(diǎn)擊 「每次加一」按鈕,咦!好用哎,完全符合預(yù)期沒(méi)毛病,但是當(dāng)你把 lodash.debounce 函數(shù)的參數(shù)改成 { leading: true, trailing: false },但是防抖時(shí)第一次觸發(fā)就立即執(zhí)行,這時(shí)候你發(fā)現(xiàn)防抖失效。思考下。。。。。。??
原因:?jiǎn)柺裁捶蓝妒∧??原因在于觸發(fā)抖動(dòng)立即執(zhí)行了 setCount 導(dǎo)致 count 改變同時(shí)引發(fā)了 useCallback 依賴(lài)項(xiàng)改變,導(dǎo)致函數(shù)重建,這時(shí)的 debounce 其實(shí)是銷(xiāo)毀 => 重建 => 銷(xiāo)毀 => 重建······無(wú)限循環(huán)??了。反之,相信你也能推理出 debounce 使用默認(rèn)值為啥是好的。
OK,總結(jié)下:debounce 只所以不能在 React-hooks 中放心使用的原因就是因?yàn)橐蕾?lài)更新的問(wèn)題。如果非要使用的話,特別注意?? hooks 依賴(lài)更新的時(shí)機(jī)。只有當(dāng)頻繁調(diào)用 handleClick 函數(shù)時(shí),立刻執(zhí)行一次相關(guān)函數(shù),所有點(diǎn)擊完成 1000ms 后釋放防抖函數(shù),為下次準(zhǔn)備。只有這種情況下能放心使用 debounce。。
既然在 React-hooks 中不能無(wú)腦使用 debounce,那我們就自己封裝一個(gè) useDebounceFn 函數(shù)。當(dāng)然你也可以去找成型的 hooks 插件,但是還是推薦研究下,因?yàn)槊嬖嚨膯?wèn),變態(tài)點(diǎn)說(shuō)不定還要手寫(xiě)。如果要使用插件這里推薦 Umi Hooks 好用,且封裝的好多 hooks 比較常用。
二、useDebounceFn 初版
之前我有一篇文章 debounce and throttle,這篇文章寫(xiě)的就是如何手寫(xiě) debounce and throttle,我這里直接把代碼拿過(guò)來(lái)了。 先看 useDebounceFn 使用示例:
import React, { useState } from "react";
import useDebounceFn from "./useDebounceFn";
export default () => {
const [ count, setCount ] = useState(0);
// useDebounceFn(fn, wait)默認(rèn)觸發(fā)方式為鼠標(biāo)最后一次離開(kāi)觸發(fā),也即不是立即觸發(fā)
const handleClick = useDebounceFn(() => {
setCount(count + 1);
}, 1000, true);
return (
<>
<h3> 計(jì)算結(jié)果: {count} </h3>
<button onClick={handleClick}>每次加一</button>
</>
);
};
useDebounceFn(fn, wait) 默認(rèn)觸發(fā)方式為:當(dāng)頻繁調(diào)用 handleClick 函數(shù)時(shí),只會(huì)在所有點(diǎn)擊完成 1000ms后執(zhí)行一次相關(guān)函數(shù),也即不是立即觸發(fā),useDebounceFn 有三個(gè)參數(shù),第一個(gè)參數(shù)表示要執(zhí)行的相關(guān)函數(shù) fn,第二個(gè)參數(shù)等待執(zhí)行時(shí)間 wait,第三個(gè)參數(shù)表示防抖是立即執(zhí)行還是頻繁調(diào)用之后最后一次執(zhí)行。灰常簡(jiǎn)單明了。接下來(lái)看 useDebounceFn 文件代碼:
function useDebounceFn(func, wait, immediate = false) {
let timeout, context, result;
/* useDebounceFn 第三個(gè)參數(shù)為 true 的時(shí)候,timeout 一直為假 */
console.log("timeout", timeout);
function resDebounced(...args) {
// 這個(gè)函數(shù)里面的this就是要防抖函數(shù)要的this
//args就是事件對(duì)象event
context = this;
// 一直觸發(fā)一直清除上一個(gè)打開(kāi)的延時(shí)器
if (timeout) clearTimeout(timeout);
if (immediate) {
// 第一次觸發(fā),timeout===undefined恰好可以利用timeout的值
const callNow = !timeout;
timeout = setTimeout(function() {
timeout = null;
}, wait);
if (callNow) result = func.apply(context, args);
} else {
// 停止觸發(fā),只有最后一個(gè)延時(shí)器被保留
timeout = setTimeout(function() {
timeout = null;
// func綁定this和事件對(duì)象event,還差一個(gè)函數(shù)返回值
result = func.apply(context, args);
}, wait);
};
return result;
};
resDebounced.cancal = function(){
clearTimeout(timeout);
timeout = null;
};
return resDebounced;
};
export default useDebounceFn;
幾乎就是全部復(fù)制粘貼過(guò)來(lái)的,現(xiàn)在我們知道useDebounceFn函數(shù)唯一的問(wèn)題就是,第三個(gè)參數(shù)為 true 的時(shí)候,沒(méi)有防抖。原因就是因?yàn)椋?code>setCount 函數(shù)立刻執(zhí)行之后,引發(fā)函數(shù)組件重新渲染,導(dǎo)致 useDebounceFn 被重新執(zhí)行,用于標(biāo)記延時(shí)器是否開(kāi)啟的標(biāo)記變量 timeout 被清空。以至于防抖失效。
好了,找到問(wèn)題那就太簡(jiǎn)單了,我們只需要解決函數(shù)無(wú)法記住 timeout 的值就 OK 了。怎么記住 timeout 的值呢?當(dāng)然是使用 useRef 啦。好了我們把代碼改下,改動(dòng)特別的小,就是用 useRef 來(lái)緩存下變量 timeout,邏輯都不用動(dòng),改動(dòng)如下:
import { useRef } from "react";
function useDebounceFn(func, wait, immediate) {
let timeout = useRef(), context, result;
console.log(timeout, "timeout");
function resDebounced(...args) {
// 這個(gè)函數(shù)里面的this就是要防抖函數(shù)要的this
//args就是事件對(duì)象event
context = this;
// 一直觸發(fā)一直清除上一個(gè)打開(kāi)的延時(shí)器
if (timeout.current) clearTimeout(timeout.current);
if (immediate) {
// 第一次觸發(fā),timeout===undefined恰好可以利用timeout的值
const callNow = !timeout.current;
timeout.current = setTimeout(function() {
timeout.current = null;
}, wait);
if (callNow) result = func.apply(context, args);
} else {
// 停止觸發(fā),只有最后一個(gè)延時(shí)器被保留
timeout.current = setTimeout(function() {
timeout.current = null;
// func綁定this和事件對(duì)象event,還差一個(gè)函數(shù)返回值
result = func.apply(context, args);
}, wait);
};
return result;
};
resDebounced.cancal = function(){
clearTimeout(timeout.current);
timeout.current = null;
};
return resDebounced;
};
export default useDebounceFn;
完美,到此一個(gè)可用的 useDebounceFn 就寫(xiě)完了,基本啥也沒(méi)干,就是使用了 hooks 的 useRef API 來(lái)緩存 timeout??, useDebounceFn 使用模范代碼為:useDebounceFn(fn, wait, immediate);。
給大家演示一下,演示代碼:
import React, { useState } from "react";
import useDebounceFn from "./useDebounceFn";
export default () => {
const [ count, setCount ] = useState(0);
const [ num, setNum ] = useState(0);
// useDebounceFn(fn, wait)默認(rèn)觸發(fā)方式為鼠標(biāo)最后一次離開(kāi)觸發(fā),也即不是立即觸發(fā)
const handleClickTrue = useDebounceFn(() => {
setCount(count + 1);
}, 1000, true);
const handleClickFalse = useDebounceFn(() => {
setNum(num + 1);
}, 1000, false);
return (
<>
<main>
<section>
<p> immediate=true</p>
<p>計(jì)算結(jié)果: {count} </p>
<button onClick={handleClickTrue}>每次加一</button>
</section>
<section>
<p> immediate=false</p>
<p>計(jì)算結(jié)果: {num} </p>
<button onClick={handleClickFalse}>每次加一</button>
</section>
</main>
</>
);
};
演示動(dòng)圖效果:

三、useDebounceFn 優(yōu)化
如果在類(lèi)組件里面我們就可以收工了,但是在 hooks 里面新出了很多用于緩存的 API,不用白不用,我們需要借助這些 API 來(lái)做下緩存優(yōu)化。
- 緩存 => 返回的 resDebounced 函數(shù)
問(wèn)題描述:當(dāng)我更改函數(shù)組件的其他狀態(tài)時(shí),會(huì)觸發(fā) useDebounceFn 函數(shù)的重建。
我稍微把上面的例子改下:
import React, { useState } from "react";
import useDebounceFn from "./useDebounceFn";
export default () => {
const [count, setCount] = useState(0);
const [num, setNum] = useState(0);
const handleClickTrue = useDebounceFn(() => {
setCount(count + 1);
}, 1000, true);
const handleClickFalse = () => {
setNum(num + 1);
};
return (
<>
<main>
<section>
<p>計(jì)算結(jié)果: {count} </p>
<button onClick={handleClickTrue}>每次加一</button>
</section>
<section>
<p>別的狀態(tài)在更新,useDebounceFn會(huì)被一直在重新創(chuàng)建</p>
<p>num 的計(jì)算結(jié)果:: {num} </p>
<button onClick={handleClickFalse}>每次加一</button>
</section>
</main>
</>
);
};
觀察到的現(xiàn)象如下:

解決問(wèn)題的辦法: 使用 useCallback 來(lái)解決。
- 緩存需要執(zhí)行的相關(guān)函數(shù)
fn等。
接下來(lái)我們肯定會(huì)使用 useCallback 函數(shù)來(lái)做 useDebounceFn 函數(shù)的緩存,但是一旦使用 useCallback 函數(shù),就要處理不屬于 useDebounceFn 函數(shù)作用域的變量,這些變量有兩條路:
- 借助
useCallback函數(shù)的第二個(gè)參數(shù),做依賴(lài)更新。 - 借助
useRef永久存貯,借助useEffect更新永久存貯。 - 其實(shí)上面一項(xiàng)和二項(xiàng)是可以相互轉(zhuǎn)換的
根據(jù)上面??我提到的解決方法,優(yōu)化的終極版源碼如下,貼心的我把注釋寫(xiě)的夠清楚了,在看不懂沒(méi)辦法了:
import { useRef, useCallback, useEffect } from "react";
function useDebounceFn(func, wait, immediate) {
const timeout = useRef();
/* 函數(shù)組件的this其實(shí)沒(méi)啥多大的意義,這里我們就把this指向func好了 */
const fnRef = useRef(func);
/* useDebounceFn 重新觸發(fā) func 可能會(huì)改變,這里做下更新 */
useEffect(() => {
fnRef.current = func;
}, [ func ]);
/*
timeout.current做了緩存,永遠(yuǎn)是最新的值
cancel 雖然看著沒(méi)有依賴(lài)項(xiàng)了
其實(shí)它的隱形依賴(lài)項(xiàng)是timeout.current
*/
const cancel = useCallback(function() {
timeout.current && clearTimeout(timeout.current);
}, []);
/* 相關(guān)函數(shù) func 可能會(huì)返回值,這里也要緩存 */
const resultRef = useRef();
function resDebounced(...args) {
//args就是事件對(duì)象event
// 一直觸發(fā)一直清除上一個(gè)打開(kāi)的延時(shí)器
cancel();
if (immediate) {
// 第一次觸發(fā),timeout===undefined恰好可以利用timeout的值
const callNow = !timeout.current;
timeout.current = setTimeout(function() {
timeout.current = null;
}, wait);
/* this指向func好了 */
if (callNow) resultRef.current = fnRef.current.apply(fnRef.current, args);
} else {
// 停止觸發(fā),只有最后一個(gè)延時(shí)器被保留
timeout.current = setTimeout(function() {
timeout.current = null;
// func綁定this和事件對(duì)象event,還差一個(gè)函數(shù)返回值
resultRef.current = fnRef.current.apply(fnRef.current, args);
}, wait);
};
return resultRef.current;
};
resDebounced.cancal = function(){
cancel();
timeout.current = null;
};
/* resDebounced 被 useCallback 緩存 */
/*
這里也有個(gè)難點(diǎn),數(shù)組依賴(lài)項(xiàng)如何天蝎,因?yàn)樗鼪Q定了函數(shù)何時(shí)更新
1. useDebounceFn 重新觸發(fā) wait 可能會(huì)改變,應(yīng)該有 wait
2. useDebounceFn 重新觸發(fā) immediate 可能會(huì)改變,應(yīng)該有 immediate
3. 當(dāng)防抖時(shí),resDebounced 不應(yīng)該讀取緩存,而應(yīng)該實(shí)時(shí)更新執(zhí)行
這時(shí)候估計(jì)你想不到用哪個(gè)變量來(lái)做依賴(lài)!被難住了吧,哈哈哈哈哈??????
這時(shí)候你應(yīng)該想實(shí)時(shí)更新,resDebounced函數(shù)里面哪個(gè)模塊一直是實(shí)時(shí)更新的。
沒(méi)錯(cuò)就是清除延時(shí)器,這條語(yǔ)句。很明顯依賴(lài)項(xiàng)就應(yīng)該是它。應(yīng)該怎么寫(xiě)呢???
提出來(lái),看我給你秀一把。
*/
return useCallback(resDebounced, [ wait, cancel, immediate ]);
}
export default useDebounceFn;
最后再給大家演示帶清除按鈕的栗子??:
還是上面用的加法器,只簡(jiǎn)單的增加兩個(gè)「清除」按鈕,代碼如下:
import React, { useState } from "react";
import useDebounceFn from "./useDebounceFn";
export default () => {
const [ count, setCount ] = useState(0);
const [ num, setNum ] = useState(0);
// useDebounceFn(fn, wait)默認(rèn)觸發(fā)方式為鼠標(biāo)最后一次離開(kāi)觸發(fā),也即不是立即觸發(fā)
const handleClickTrue = useDebounceFn(() => {
setCount(count + 1);
}, 3000, true);
const handleClickFalse = useDebounceFn(() => {
setNum(num + 1);
}, 1000, false);
return (
<>
<main>
<section>
<p> immediate=true</p>
<p>計(jì)算結(jié)果: {count} </p>
<button onClick={handleClickTrue}>每次加一</button>
<button onClick={handleClickTrue.cancal}>清空</button>
</section>
<section>
<p> immediate=false</p>
<p>計(jì)算結(jié)果: {num} </p>
<button onClick={handleClickFalse}>每次加一</button>
<button onClick={handleClickFalse.cancal}>清空</button>
</section>
</main>
</>
);
};
演示效果:
-
immediate=true時(shí),頻繁觸發(fā),立即執(zhí)行相關(guān)函數(shù),清除表現(xiàn)為可以再次立即執(zhí)行相關(guān)函數(shù)不用等待。 -
immediate=false時(shí),頻繁觸發(fā),最后一次離開(kāi)等待 wait 秒執(zhí)行相關(guān)函數(shù),清除表現(xiàn)為無(wú)任何結(jié)果,就像沒(méi)觸發(fā)一樣。

三、最后一點(diǎn)思考??
第一天的白天,我遇到如何在 React hooks 中防抖,本以為不是那么難,晚上學(xué)習(xí)總結(jié)下就解決了,結(jié)果晚上創(chuàng)建完文章,就寫(xiě)了個(gè)碎碎念和前言,突然發(fā)現(xiàn)沒(méi)有能力去寫(xiě) 如何在 React hooks 中防抖 了,沒(méi)研究明白,導(dǎo)致沒(méi)有任何頭緒 ????。歷經(jīng)二次大創(chuàng)作才算完成?,中間小修了好多次,還是挺費(fèi)神的呃呃呃呃。
第二天第二次,整理結(jié)果。當(dāng)前時(shí)間 Thursday, September 24, 2020 01:10:07
本來(lái)這個(gè)小標(biāo)題是另一個(gè)思路實(shí)現(xiàn) useDebounceFn,但是去看了 Umi Hooks 的 useDebounceFn 源碼。捋一捋它的思路,把代碼刪刪減減,發(fā)現(xiàn)和我的思路差不多,被它的變量命名糊弄住了??。提出它的源代碼如下:
import { useCallback, useEffect, useRef } from 'react';
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); }
function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } }
var useUpdateEffect = function useUpdateEffect(effect, deps) {
var isMounted = useRef(false);
useEffect(function () {
if (!isMounted.current) {
isMounted.current = true;
} else {
return effect();
}
}, deps);
};
function useDebounceFn(fn, deps, wait) {
var _deps = Array.isArray(deps) ? deps : [];
var _wait = typeof deps === 'number' ? deps : wait || 0;
var timer = useRef();
var fnRef = useRef(fn);
fnRef.current = fn;
var cancel = useCallback(function () {
if (timer.current) {
clearTimeout(timer.current);
}
}, []);
var run = useCallback(function () {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
cancel();
timer.current = setTimeout(function () {
fnRef.current.apply(fnRef, args);
}, _wait);
}, [_wait, cancel]);
useUpdateEffect(function () {
run();
return cancel;
}, [].concat(_toConsumableArray(_deps), [run]));
useEffect(function () {
return cancel;
}, []);
return {
run: run,
cancel: cancel
};
}
export default useDebounceFn;
不同的是它給出了另一種用法 useDebounceFn 合理使用 deps :
使用 deps 可以實(shí)現(xiàn)和 run 一樣的效果。如果 deps 變化,會(huì)在所有變化完成 1000ms 后執(zhí)行一次相關(guān)函數(shù)。
/* TS 寫(xiě)法 */
const {
run,
cancel
} = useDebounceFn(
fn: (...args: any[]) => any,
deps: any[],
wait: number
);
我感覺(jué)這個(gè)實(shí)用性不是很強(qiáng),為啥呢?來(lái)看看官網(wǎng)給出的示例:
import React, { useState } from 'react';
import { Button, Input } from 'antd';
import { useDebounceFn } from '@umijs/hooks';
export default () => {
const [value, setValue] = useState();
const [debouncedValue, setDebouncedValue] = useState();
/* 用一個(gè)變量去更新另一變量,有這個(gè)需求直接使用 useDebounce 了 */
const { cancel } = useDebounceFn(
() => {
setDebouncedValue(value);
},
[value],
1000,
);
return (
<div>
<Input
value={value}
onChange={e => setValue(e.target.value)}
placeholder="Typed value"
style={{ width: 280 }}
/>
<p style={{ margin: '16px 0' }}>
<Button onClick={cancel}>Cancel Debounce</Button>
</p>
<p>DebouncedValue: {debouncedValue}</p>
</div>
);
};
用一個(gè)變量去更新另一變量,有這個(gè)需求直接使用 useDebounce 了,另外還有一個(gè)問(wèn)題就是,大多數(shù)業(yè)務(wù)就類(lèi)似我們的加法器,需要更新自己的 state。我們套下示例,會(huì)發(fā)現(xiàn)加法器點(diǎn)擊一次就一直自動(dòng)更新了。原因在于: useUpdateEffect 函數(shù)通過(guò) useEffect 的依賴(lài)更新,調(diào)用了 run ,run 函數(shù)調(diào)用了 fn,fn 函數(shù)又調(diào)用了 setCount,導(dǎo)致 count 更新, count 最后去觸發(fā) useUpdateEffect 的 useEffect 鉤子。從而無(wú)限循環(huán)了??
import React, { useState } from "react";
import { useDebounceFn } from '@umijs/hooks';
export default () => {
const [ count, setCount ] = useState(0);
// 頻繁調(diào)用 run,但只會(huì)在所有點(diǎn)擊完成 1000ms 后執(zhí)行一次相關(guān)函數(shù)
const { run : handleClick } = useDebounceFn(() => {
setCount(count + 1);
}, [ count ], 1000);
return (
<>
<main>
<section>
<p> 頻繁調(diào)用 run,但只會(huì)在所有點(diǎn)擊完成 1000ms 后執(zhí)行一次相關(guān)函數(shù)</p>
<p>計(jì)算結(jié)果: {count} </p>
<button onClick={handleClick}>每次加一</button>
</section>
</main>
</>
);
};
useUpdateEffect 這個(gè)函數(shù)也不是一點(diǎn)用都沒(méi)有,我們可以用它的思路來(lái)實(shí)現(xiàn) useDebounce,用來(lái)防抖一個(gè)變量。
四、實(shí)現(xiàn) useDebounce
例如頻繁輸入,輸出結(jié)果 DebouncedValue 只會(huì)在輸入結(jié)束 2000ms 后變化。

測(cè)試 Demo 骨架:
import React, { useState } from "react";
import useDebounce from "./useDebounce";
import { Input } from 'antd';
export default () => {
const [value, setValue] = useState("");
const debouncedValue = useDebounce(value, 2000);
return (
<>
<Input value={value} onChange={(e) => setValue(e.target.value)} />
<h3> 輸入的值: {debouncedValue} </h3>
</>
);
};
借用 useDebounceFn 函數(shù),封裝的 useDebounce 函數(shù):
import { useEffect, useRef, useState } from "react";
import useDebounceFn from "./useDebounceFn";
function useDebounce(value, wait) {
const isMounted = useRef(false);
const [state, setState] = useState(value);
const effect = useDebounceFn(() => {
setState(value)
}, wait);
/*
useState 已經(jīng)初始化過(guò)value,所以u(píng)seEffect的componentDidMount沒(méi)用了
借用useRef 讓 useEffect 只負(fù)責(zé) componentDidUpdate
*/
useEffect(function () {
if (!isMounted.current) {
isMounted.current = true;
} else {
return effect();
};
}, [value, wait]);
return state;
};
export default useDebounce;
徹底寫(xiě)完了,又學(xué)到不少新東西,開(kāi)森 ??。
最后一次更新時(shí)間 Thursday, September 24, 2020 18:02:28