あんまり知られてないかもしれんけど、JSDocのコメントを書くことでもTypeScriptの恩恵は受けられる。
より正確には、
- JSDocコメントを書くことで
- `.js`ファイルであっても
- TypeScriptのLanguage Serverの機能を(一部)利用できる
というわけ。
で、これが結構便利で最近はよく使ってるのです。
ただ、いわゆるジェネリクスを扱いたいときに困ったことがあったので、それについてのメモを。
ジェネリクスは不完全なサポート
たとえば、コレは動作する。
/** * @template T * @param {T} x * @return {T} */ function id(x) { return x; } const a = id("string"); const b = id(123); const c = id({});
`@template`で予め名前を宣言しておけば、こんな風に関数をジェネリクスで定義することはできる。
TSで書くと、`function id(x: T): T { return x; };`相当のもの。
けど、それ以外のジェネリクスに関することができなくて、単に型だけを定義することもできない。
type Result<Resp> = { ok: true, data: Resp };
こういう型は定義できないし、クラスでも同様に使えない。
どうやらこれは現状の仕様らしい。
Allow to explicitly pass type parameters via JSDoc · Issue #27387 · microsoft/TypeScript · GitHub
困った例
具体的な例でいうと、Reactの`useState()`を使うときに、その値が推論できない場合。
// 推論できる const [count, setCount] = useState(0); // 推論できない const [items, setItems] = useState([]); // Item[] const [error, setError] = useState(null); // or Error
TSだったならばこう書けるところ、JSDoc TSではそれができない。
const [items, setItems] = useState<Item[]>([]); const [error, setError] = useState<Error | null>();
これを解決するための方法は現状ないらしく、ワークアラウンドとしてはこうするしかない。
/** @type {Item[]} */ const initItems = []; const [items, setItems] = useState(initItems); // or const [error, setError] = useState( /** @type {Error | null} */ (null) );
というように、
- 別の所で推論できるようにした変数を渡す
- キャストする
このどっちかしかないらしい。
それでも便利なので
訳あってTSが使えない・使いたくないときに、局所的にでも手堅く書ける選択肢があるというのは嬉しいこと。
型の補完ができるのであれば、設定ファイルをわざわざTSで書く必要もないし、必要十分ってわけで。
これだとJSのまま書けるので、TSにコンパイルされたときのオーバーヘッドもないので、コードを小さく保ちたいライブラリを作る時とかにも便利。
(いまどきどれだけの人がコンパイル後のコードを意識しながらコードを書いてるのやらって感じではあるけど)
(そういえば昔Flowっていうのがあったな・・元気かな・・)
いきなりTSはちょっと・・っていう初学者にもとてもいいと思うし、割とオススメかなと思ってる今日この頃。
まぁ最初からTSでやればよかった・・ってなったら、その時は素直にTSにしましょう!