博客 / 詳情

返回

⑤react-ahooks源碼分析之useDebounceFn和useDebounce

源碼鏈接:https://github.com/alibaba/ho...

概述

首先,useDebounceFn和useDebounce都是一個用於添加防抖功能的hook,不同之處在於useDebounceFn給函數添加防抖,而useDebounce用來給值添加防抖。

防抖

某些時候,我們會無法控制所編寫事件的觸發頻率;比如搜索引擎的搜索框對於輸入內容的實時反饋,以及一些根據實時數據動態更新的組件。如果在短時間內觸發大量事件,可能會引起閃爍甚至卡頓。

防抖(Debounce)這個概念是解決此類問題的一種方法(其他的方法比如節流)。

防抖,控制事件只會在:觸發該事件,並在固定時間內未觸發第二次時,才會執行。若短時間內該事件被連續觸發多次,則會在最後一次觸發的固定時間後再執行此事件。

useDebounceFn

該hook用來包裝所需要的函數,給函數添加防抖動的功能。

簡單用法

以下的代碼將會在點擊按鈕並1000毫秒內未再次點擊按鈕時,將value變更為2。

import { useDebounceFn } from 'ahooks';
import React, { useState } from 'react';

export default () => {
  const [value, setValue] = useState(0);
  const { run } = useDebounceFn(
    (newValue) => {
      setValue(newValue);
    },
    {
      wait: 1000,
    leading
    },
  );

  return (
    <div>
      <p style={{ marginTop: 16 }}> Clicked count: {value} </p>
      <button type="button" onClick={run(2)}>
        chuange to 2
      </button>
    </div>
  );
};

參數説明

配置參數

  • wait:防抖等待時間,默認值為1000ms(number)
  • leading:是否在延遲開始前調用函數,默認為false
  • trailing:是否在延遲開始後調用函數,默認為true
  • maxWait:最大等待時間,單位為ms

結果參數

  • run:觸發執行傳入hook的函數,參數即為函數需要的參數
  • cancel:取消當前防抖
  • flush:立即調用當前防抖函數

源碼分析

isDev

if (isDev) {
    if (!isFunction(fn)) {
      console.error(`useDebounceFn expected parameter is a function, got ${typeof fn}`);
    }
  }

isDev是自定義的一個環境變量,用來判斷當前是否處於開發環境。

useLatest

const fnRef = useLatest(fn);
useLatest也是ahooks的一個hook,一般是用來返回某個state的最新值,避免閉包問題(比如上一篇文章中提到過的,useCallback的閉包陷阱)。

debounce

  const debounced = useMemo(
    () =>
      debounce(
        (...args: Parameters<T>): ReturnType<T> => {
          return fnRef.current(...args);
        },
        wait,
        options,
      ),
    [],
  );

debounce是lodash中的方法,用來解決包裝函數解決防抖問題。這裏使用useMemo緩存debounce這個方法,同樣是為了減少消耗。下面封裝run、cancel、flush時,也是直接使用debounce的方法。

Parameters是typescript的告誡類型,用於獲取函數T的參數類型,而ReturnType則是用來獲取函數T的返回類型。

fnRef是使用useLatest封裝好的,傳入函數的最新值。

useUnmount

 useUnmount(() => {
    debounced.cancel();
  });

useUnmount也是ahooks封裝好的hook,用來設置在組件卸載時執行的函數;為了防止內存泄露,當組件卸載時需要取消對debounce的調用。

附上源碼


import debounce from 'lodash/debounce';
import { useMemo } from 'react';
import type { DebounceOptions } from '../useDebounce/debounceOptions';
import useLatest from '../useLatest';
import useUnmount from '../useUnmount';
import { isFunction } from '../utils';
import isDev from '../utils/isDev';

type noop = (...args: any[]) => any;

function useDebounceFn<T extends noop>(fn: T, options?: DebounceOptions) {
//判斷是否開發環境
  if (isDev) {
    if (!isFunction(fn)) {
      console.error(`useDebounceFn expected parameter is a function, got ${typeof fn}`);
    }
  }

  const fnRef = useLatest(fn);

  const wait = options?.wait ?? 1000;

  const debounced = useMemo(
    () =>
      debounce(
        (...args: Parameters<T>): ReturnType<T> => {
          return fnRef.current(...args);
        },
        wait,
        options,
      ),
    [],
  );

  useUnmount(() => {
    debounced.cancel();
  });

  return {
    run: debounced,
    cancel: debounced.cancel,
    flush: debounced.flush,
  };
}

export default useDebounceFn;

useDebounce

分析過useDebounceFn後,useDebounce的源碼較為簡單,直接用useState創建一個debounced,然後使用useDebounceFn封裝它的更新方法setDebounced即可。

useEffect

該hook在每次依賴項改變時會執行,與usecallback不同的是,當依賴項為空時,該hook每次重新渲染都會執行。用在這裏,當傳入的參數改變時就調用一次debounce的run(當然還是遵循防抖動的規則)

源碼

function useDebounce<T>(value: T, options?: DebounceOptions) {
  const [debounced, setDebounced] = useState(value);

  const { run } = useDebounceFn(() => {
    setDebounced(value);
  }, options);

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

  return debounced;
}
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.