0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TypeScriptでthrottleとdebounce関数を実装する

Posted at

概要

scrollイベントやresizeイベントなど頻発するイベントに対して、毎回ハンドラーを実行するのが勿体無いし、パフォーマンス面に悪い影響が出る恐れはあるため、それを遅延するような関数が登場する。

基本概念

1. throttleの意味

  • 何回に呼ばれても、指定の間隔時間内に最後の呼出だけが実行される
  • 指定の間隔時間内に必ず1回が実行される

利用例scrollイベントハンドラーなどで使用される
解説スクロール途中にハンドラー処理により画面表示内容が変わるような時に利用するのが適する

つまり、実行する頻度を避けるが、一定の間隔で必ず実装する。

2. debounceの意味

  • 何回に呼ばれても、最後の呼出だけが有効になる
  • 最後の呼出の遅延時間を経ってたら実行される
  • 遅延時間内に新たな呼出があったら、遅延時間がリセットされ、0から数える
  • 極端に言うと、遅延時間の直前に連続で新たに呼び出すと永遠に呼ばれなくなる

利用例サジェストのキーワード入力フィールドにキーワードを変更する時のAPI通信処理
解説途中のキーワードで取得したデータが必ず捨てられるのでdebounceすることで無駄な通信が避けられる

つまり、相応しい遅延時間を設けることで、途中の連続操作による呼出を全て捨てる。

実装例

react componentunmountされた後、遅延された処理を呼出しても意味ない場合が多いため、unmountする前にそれをキャンセルした方が良いのはほとんどだと思われる。従って、キャンセルできる仕組みを設ける。

TypeScriptでの実装例をメモする。

1. throttleの実装例

export const throttle = <Args extends unknown[]>(
  fn: (...args: Args) => void,
  interval: number,
): [(...args: Args) => void, () => void] => {
  // クロージャでタイマーIDと処理時間を覚える
  let timerId: ReturnType<typeof setTimeout>;
  let lastExecTime = 0;

  // 遅延処理を入れて元の関数をラップする
  const start = (...args: Args) => {
    const currentTime = Date.now();

    const execute = () => {
      fn(...args);
      lastExecTime = Date.now();
    };

    // 時間になってもtimerが実行されていない場合があるため、念のためクリアする
    clearTimeout(timerId);

    if (lastExecTime + interval <= currentTime) {
      // 前回の実行から指定の間隔時間を経ったら、関数を実行する
      execute();
    } else {
      // 前回の実行から指定の間隔時間を経ってなければ、タイマーを登録する
      const newInterval = lastExecTime + interval - currentTime;
      timerId = setTimeout(execute, newInterval);
    }
  };

  // 遅延された処理をキャンセル
  const cancel = () => {
    clearTimeout(timerId);
  };

  return [start, cancel];
};

2. throttleの実装例

export const debounce = <Args extends unknown[]>(
  fn: (...args: Args) => void,
  delay: number,
): [(...args: Args) => void, () => void] => {
  // クロージャでタイマーIDを覚える
  let timerId: ReturnType<typeof setTimeout>;

  // 遅延処理を入れて元の関数をラップする
  const start = (...args: Args) => {
    clearTimeout(timerId);
    timerId = setTimeout(() => {
      fn(...args);
    }, delay);
  };

  // 遅延された処理をキャンセル
  const cancel = () => {
    clearTimeout(timerId);
  };

  return [start, cancel];
};

まとめ

純粋なthrottledebounce関数を色んな場面で活用することで、無駄な処理をなくして、より効率的な実装ができる。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?