LoginSignup
2
1

はじめに

実務でsetTimeoutを使うことになりました。
かなりTypeScriptの型推論に振り回されたので、共有したいと思います。

自己紹介

私は2023年9月1日にフロントエンドエンジニアとして自社開発のSaaS企業に入社しました。
未経験独学で、薬剤師からの転職です。
詳しくはこちらをご覧ください。

結論

setTimeoutの型注釈は以下のように書く。

const timeoutId = useRef<ReturnType<typeof setTimeout>>();

具体例で説明

背景

ある条件を満たした時に、フラッシュメッセージを表示するという実装を行う。
まずは表示するロジックを作りたい。
10秒後に消すという要件を満たすために、初めてsetTimeoutを使うことになった。

要件

  • フラッシュメッセージを表示する
  • 表示したフラッシュメッセージは10秒後に消す

設計

  • フラッシュメッセージの表示ロジック
    • 表示状態を表すstateを定義(boolean)
  • 10秒後に消す
    • setTimeout使う
    • timeoutIDrefに入れて管理する

問題

以下、私が書いたソースコードです。

const [showFlashMessage, setShowFlashMessage] = useState(false);
const timeoutIdRef = useRef<number>();

// フラッシュメッセージを表示
setShowFlashMessage(true);
// 10秒後に非表示にする
timeoutIdRef.current = setTimeout(() => {
  setShowFlashMessage(false);
}, 10000);

まずは表示状態を表すstateを宣言しました。
そして条件を満たした時に、setShowFlashMessageでstateをtrueに切り替えて表示します。
次に、setTimeoutを使って、10秒(=10000ミリ秒)後に非表示にするようにstateをfalseに切り替えて、非表示にします。
timeoutIDは設計通り、refに入れています。
返される timeoutID は正の整数値なので、型はnumberとしました。

しかし、ここでTypeScriptのエラーが出ました。

型 'Timeout' を型 'number' に割り当てることはできません。ts(2322)

🤔(あれ…number型じゃないの??)
正しいtimeoutIdの型と解決方法を調べてみました。

setTimeoutの型

結論から言うと、以下のように書けば良いそうです。

const timeoutId = useRef<ReturnType<typeof setTimeout>>();

こちらに回答が載っていました。
世界にも同じように悩んだ方がいたんですね!

どうやら原因は、TypeScriptのコンパイラがsetTimeout関数の型をNode.js環境向けに推論していることにありそうです。
ブラウザではnumberを返すのに対して、Node.jsではTimer型(NodeJS.Timer)を返します。
そのため、ブラウザで正常に動作したとしても、Node.js環境ではエラーになってしまいます。
よって、解決するためには、プラットフォームに関係なく動くような型指定が必要です。

ReturnType<typeof setTimeout>で何をしているのかを考えてみます。

まずはtypeofでsetTimeoutの型を取得します。
次にReturnTypeは、関数の型から戻り値の型を取得するTypeScriptのユーティリティ型の1つです。よってReturnType<typeof setTimeout>と組み合わせることで、setTimeout関数の戻り値の型を取得できるというわけです。
これにより、number型やNodeJS.Timer型など、setTimeout関数が返す型が正確に指定されるため、プラットフォームに関係なく型を指定することができました。

実装

timeoutIDの型を変更してエラー解消です!

const [showFlashMessage, setShowFlashMessage] = useState(false);
- const timeoutIdRef = useRef<number>();
+ const timeoutIdRef = useRef<ReturnType<typeof setTimeout>>();

// フラッシュメッセージを表示
setShowFlashMessage(true);
// 10秒後に非表示にする
timeoutIdRef.current = setTimeout(() => {
  setShowFlashMessage(false);
}, 10000);

おわりに

TypeScriptの型推論に振り回された話でした。
結果的に、特殊な型指定の方法を学ぶことができました!
今後も出てきそうな実装なので、頭に入れておきたいと思います。

それにしても、同じように悩んだ人が世界のどこかにいるというのは、ちょっと嬉しいですね。
大変助かりました🙏

2
1
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
2
1