はじめに
実務でsetTimeoutを使うことになりました。
かなりTypeScriptの型推論に振り回されたので、共有したいと思います。
自己紹介
私は2023年9月1日にフロントエンドエンジニアとして自社開発のSaaS企業に入社しました。
未経験独学で、薬剤師からの転職です。
詳しくはこちらをご覧ください。
結論
setTimeoutの型注釈は以下のように書く。
const timeoutId = useRef<ReturnType<typeof setTimeout>>();
具体例で説明
背景
ある条件を満たした時に、フラッシュメッセージを表示するという実装を行う。
まずは表示するロジックを作りたい。
10秒後に消すという要件を満たすために、初めてsetTimeout
を使うことになった。
要件
- フラッシュメッセージを表示する
- 表示したフラッシュメッセージは10秒後に消す
設計
- フラッシュメッセージの表示ロジック
- 表示状態を表すstateを定義(boolean)
- 10秒後に消す
-
setTimeout
使う -
timeoutID
をref
に入れて管理する
-
問題
以下、私が書いたソースコードです。
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の型推論に振り回された話でした。
結果的に、特殊な型指定の方法を学ぶことができました!
今後も出てきそうな実装なので、頭に入れておきたいと思います。
それにしても、同じように悩んだ人が世界のどこかにいるというのは、ちょっと嬉しいですね。
大変助かりました🙏