如何在 React hooks 中防抖

碎碎念: 今天公司新入職一位人事小姐姐,打破了我在公司的記錄——「公司最小的員工」。絕對(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 hooksuseState 寫(xiě)一個(gè)簡(jiǎn)單的加法器,效果如下:

React hooks 版簡(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)干,就是使用了 hooksuseRef 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 immediate 分別為 true 和 false 演示示例

三、useDebounceFn 優(yōu)化

如果在類(lèi)組件里面我們就可以收工了,但是在 hooks 里面新出了很多用于緩存的 API,不用白不用,我們需要借助這些 API 來(lái)做下緩存優(yōu)化。

  1. 緩存 => 返回的 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)象如下:

當(dāng)我更改函數(shù)組件的其他狀態(tài)時(shí),會(huì)觸發(fā) useDebounceFn 函數(shù)的重建動(dòng)圖演示

解決問(wèn)題的辦法: 使用 useCallback 來(lái)解決。

  1. 緩存需要執(zhí)行的相關(guān)函數(shù) fn 等。

接下來(lái)我們肯定會(huì)使用 useCallback 函數(shù)來(lái)做 useDebounceFn 函數(shù)的緩存,但是一旦使用 useCallback 函數(shù),就要處理不屬于 useDebounceFn 函數(shù)作用域的變量,這些變量有兩條路:

  1. 借助 useCallback 函數(shù)的第二個(gè)參數(shù),做依賴(lài)更新。
  2. 借助 useRef 永久存貯,借助 useEffect 更新永久存貯。
  3. 其實(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 HooksuseDebounceFn 源碼。捋一捋它的思路,把代碼刪刪減減,發(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)用了 runrun 函數(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 后變化。

頻繁輸入,輸出結(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容