debounceのTypescriptコード

以下の記事のdebounce関数を使用したいと思ったのですが、使用する環境はTypescriptなもんでそのままコピできません。コピペで仕事ができない辛い時代になってきました。

https://www.freecodecamp.org/news/javascript-debounce-example/

なので調整してみました。

## 完成品はこちら

```typescript
function debounce<T, F extends (...args: any[]) => void>(
  thisArg: T,
  func: F,
  timeout = 300
): (...args: Parameters<F>) => void {
  let timer: ReturnType<typeof setTimeout>;
  return (...args: Parameters<F>): void => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(thisArg, args);
    }, timeout);
  };
}

function saveInput(): void {
  console.log('Saving data');
}

const processChange: (...args: any[]) => void = debounce(undefined, () => saveInput());
```

## Q. どうやって解決したの？

A. ChatGPT です。
といってもそのまま「Tyepscriptに変換して！」ってお願いしても、一発でエラーのないコードは生成されないもんです。
最初にいただいたコードはこちら。

```typescript
function debounce<F extends (...args: any[]) => void>(func: F, timeout = 300): (...args: Parameters<F>) => void {
  let timer: number | undefined;
  return (...args: Parameters<F>): void => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(this, args);
    }, timeout);
  };
}

function saveInput(): void {
  console.log('Saving data');
}

const processChange: (...args: any[]) => void = debounce(() => saveInput());
```

このままだとtypescriptエラーが発生します。

### setTimeout の返り値の型

まず `timer = setTimeout(() => {` の行で、
`Type 'Timeout' is not assignable to type 'number'.ts(2322)`
というエラーが発生。調べてみると、環境によって `number` だったり `NodeJS.Timeout` だったりするので、Typescriptに型推論させたほうが良いとのこと。
なので

```diff_typescript
- let timer: number | undefined;
+ let timer: ReturnType<typeof setTimeout>;
```

とする。

### `this` の型

次に `func.apply(this, args);` の行で
`'this' implicitly has type 'any' because it does not have a type annotation.ts(2683)`
というエラーが発生しました。

this を undefined にしたり

```diff_typescript
- func.apply(this, args);
+ func.apply(undefined, args);
```

this の型を 明示的に any にしたり

```diff_typescript
- return (...args: Parameters<F>): void => {
+ return function (this: any, ...args: Parameters<F>): void {
```

のようにしてもいいかなーと思ったのですが、せっかく apply メソッドを使っているのでちゃんと活用したいところ。

ChatGPTさんが、ジェネリクスを使いなさいと、コードまでくださったので、その通りにします。

```diff_typescript
- function debounce<F extends (...args: any[]) => void>(
-   func: F,
-   timeout = 300
- ): (...args: Parameters<F>) => void {
...
-     func.apply(this, args);
...
- const processChange: (...args: any[]) => void = debounce(() => saveInput());


+ function debounce<T, F extends (...args: any[]) => void>(
+   thisArg: T,
+   func: F,
+   timeout = 300
+ ): (...args: Parameters<F>) => void {
...
+     func.apply(thisArg, args);
...
+ const processChange: (...args: any[]) => void = debounce(undefined, () => saveInput());
```

呼び出す側で thisArg を指定する必要がありますが、オブジェクトのメソッドでも使用したいのでこのようにする。

## おまけ

`<button onclick="processChange()">Click me</button>` のように、onclickハンドラ内で直接`processChange`を呼び出すときは、以下のようにしてグローバル変数に登録をする。
あまりこういうことしたことがないので、備忘録。

```typescript
(window as any).processChange = debounce(window, () => saveInput());
```


## 参考記事
[setTimeoutの型注釈がnumber型ではエラーになる理由 #React - Qiita](https://qiita.com/odendayoko/items/c3c7fee1791597fa7efe)
[this - JavaScript | MDNMDN Web DocsMDN logoMozilla logo](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/this)