Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
387
Help us understand the problem. What is going on with this article?
@uhyo

2021年のTypeScript環境構築で絶対入れるべき「better-typescript-lib」の紹介

こんにちは。この記事は筆者が開発した「better-typescript-lib」を宣伝する記事です。これは、導入するだけでTypeScriptプログラムがより型安全になるという素晴らしいライブラリです。あくまで型定義なので、導入してもランタイムの挙動は何も変わらず、バンドルサイズなどへの影響もありません。

better-typescript-libの導入法

GitHubにも書いてありますが、ここにも記載しておきます。まずbetter-typescript-libをインストールしましょう。

npm i -D better-typescript-lib

次に、tsconfig.jsonを編集してlibを除去し、代わりにnoLibtrueにしましょう。

-  "lib": ["es5", "dom"]
+  "noLib": true

最後に、プログラムのどこか(エントリーポイントとなるファイルが良いでしょう)に次のような記述を追加してbetter-typescript-libを読み込みましょう。何を読み込むかは、もともとのlibの記述と同様にします。

/// <reference path="./node_modules/better-typescript-lib/lib.es5.d.ts" />
/// <reference path="./node_modules/better-typescript-lib/lib.dom.d.ts" />

以上で終了です。

better-typescript-libは何をしてくれるのか

better-typescript-libは、TypeScriptの標準ライブラリをより安全性が高くなるように修正してくれるものです。TypeScriptの標準ライブラリとは、JavaScript及びDOMにもともと備わっている機能の型定義のことです。例えばparseIntとかJSON.parseとかdocument.querySelectorといったものは特に追加のライブラリを導入しなくてもTypeScriptプログラムで使用可能でちゃんと型が付きますが、これらに型が付くのはTypeScriptコンパイラに付属する標準ライブラリが型定義を提供してくれているからです。

TypeScriptの標準ライブラリは、歴史的経緯や利便性の重視などの理由により、完璧に型安全でない点がいくつもあります。代表的なものは、JSON.parseの返り値がany型となっていることです。

type FooObj = { foo: number };

const obj: FooObj = JSON.parse("{}");
// 本当はundefined😨
const num: number = obj.foo;

このように、JSON.parseの結果には好き勝手な型を付けることができてしまうため、実態と異なる型となり型安全性を破壊するのが非常に簡単です。

better-typescript-libを導入すると、JSON.parseの返り値の型はanyではなくJSONValue型に修正されます。その結果、安全性が保証されない代入に対してこのように正しくコンパイルエラーが発生します。

type FooObj = { foo: number };
// エラー: Type 'JSONValue' is not assignable to type 'FooObj'.
const obj: FooObj = JSON.parse("{}");

better-typescript-libの存在下ではJSON.parseを使うために取れる選択肢は2つです。io-tsのようなランタイム型チェックライブラリと組み合わせることで完全な安全性を得るか、あるいはasを使ってJSONValue型を目的の型に変えることです。

type FooObj = { foo: number };

const obj = JSON.parse("{ foo: 3 }") as FooObj;
const num: number = obj.foo;

JSON.parseによって得られる型が(人間にとっては)明らかな場合にはasを使うのも有効でしょう。その場合でも、従来はanyによって隠されていた危険性がasという形で明確に現れるようになったのは大きな進歩です。

このように、より安全な型定義は、プログラムのバグを未然に防ぐのはもちろん、危険なプログラムを書くことに対する責任をTypeScript(の標準ライブラリ)から人間の側に移します。これによりプログラムの安全性をプログラマがより精密にコントロールすることができるようになり、本質的に危険な操作が必要な場合でもそれがどこで発生しているのかを把握・注視することができます。

better-typescript-libの安全性の他の例

better-typescript-libでは、筆者がTypeScriptの標準ライブラリを見て目についた危険性を一通り修正してあります(この記事執筆時点ではlib.dom.d.tsにはまだ手をつけていませんが)。多いのはanyunknownに修正するようなものです。

例えば、TypeScriptの標準ライブラリではnew Array()Array<any>となってしまいます。また、new Array(10).fill(123)というよく使われるパターンも同様にArray<any>となり危険です。better-typescirpt-libを導入することで、これはより安全なArray<unknown>という型になります。後者のコードはnew Array<number>(10).fill(123)とすればArray<number>になります。

なぜbetter-typescript-libが必要なのか

TypeScriptの標準ライブラリに型安全性の問題があるならば、本来は安全性を直すのはTypeScript本体側であるべきです。これまでも、標準ライブラリの修正が全く行われなかった訳ではありません。

しかしながら、その際には後方互換性の問題が付きまといます。特に、先ほどのJSON.parseanyを返す件などは深く考えずに利用してしまっている人が多いでしょう。このような影響範囲の大きい変更は行われない傾向にあります。以前には、Promise.allの型定義を改善した結果後方互換性の問題が発覚し、リバートされたという例もあります。

過去には、コンパイラオプションが指定されたときのみより安全な型定義が適用されるようにした例もあります(--strictBindCallApply)。ただ、これもコンパイラオプションの有り無しによって異なる型定義が読み込まれるような実装にはなっておらず1、そのような実装はアーキテクチャ上難しいようです。また、コンパイラオプションをむやみに追加するのは望ましくないことから、anyを1つや2つunknownに変える程度の修正を行うのも難しいでしょう。

そこで、改善された型定義を別のライブラリに切り出すことで、TypeScript本体の後方互換性に縛られずに柔軟な運用が可能となります。それがbetter-typescript-libの役割です(切り出すという書き方をしましたが、better-typescript-libはTypeScript公式とは何の関係もない非公式のライブラリです)。

また、TypeScriptは今も進化を続けており、今年(2020年)もVariadic Tuple TypesやTemplate Literal Typesと言った大胆な機能追加が行われました。既存の型定義の中にもこれらの機能を使って書き直せるものがあるはずですが、前述の後方互換性の問題からむやみに型定義の改善は行われません。これもbetter-typescript-libが拾うことができる役割です。例えば、TypeScriptの型定義にはPromise.allに引数の数に合わせて10個くらいのオーバーロードがありますが、better-typescript-libではTypeScript 4.0で追加されたVariadic Tuple Typesを用いてこれを1つだけにしました。

ところで、タイトルに「絶対入れるべき」と書きましたが、一つ例外があります。それは、あなたが別に型安全性にこだわらない場合です。例えばJSON.parseの型はanyのほうが便利なので嬉しいと思う場合は、better-typescript-libを導入する必要はないでしょう。

逆に、できる限りの型安全性を保ちながらTypeScriptを使いたいという場合は、ぜひbetter-typescript-libを導入するべきです。理由は単純で、better-typescript-libを入れたほうがより型安全だからです。

better-typescript-libを使う際の注意

そんな絶対入れるべきなbetter-typescript-libですが、いくつか紹介しておくべき注意点があります。

1. TypeScriptのバージョンを上げる際はbetter-typescript-libも対応バージョンに上げる

TypeScriptのバージョンを上げた際は新しい型定義が標準ライブラリに追加されることがあります。better-typescript-libをそれを取り込んだバージョンに上げないと、新しい機能が使えない場合があります。

また、better-typescript-libがTypeScriptの新バージョンに対応するまで待つ必要があるかもしれません。

2. better-typescript-libのバージョンを上げると新たなコンパイルエラーが発生することがある

TypeScript本体が後方互換性に縛られている分、better-typescript-libはより高い頻度で改善を行いたいと考えています。そのため、better-typescript-libのバージョンを上げる際は、改善の結果発生した新たなコンパイルエラーに対処する必要があるかもしれません。

型安全性のためなら喜んでコンパイルエラーに対処しますという志のある方なら、better-typescript-libの使用を妨げるものは何もないでしょう。

3. 既存のコードベースに導入する際は多少の作業が必要

better-typescript-libは新しいTypeScriptプロジェクトに導入するのが最も向いていますが、既存プロジェクトでも使いたいという場合は導入が可能です。その場合、大抵の場合はbetter-typescript-libの導入によってコードの危険な部分が炙り出され、コンパイルエラーが発生するでしょう。これらに対処する必要があります。

コードの性質にもよりますが、筆者が約10万行のコードベースで試してみた際は30個程度のコンパイルエラーが発生しました。

まとめ

この記事では、TypeScriptの標準ライブラリをより型安全にするライブラリ「better-typescript-lib」を宣伝しました。

JSON.parseの返り値がany」はTypeScriptの間の抜けたところを表す鉄板ネタでしたが、2021年からはそれは通用しません。better-typescript-libがあるのですから。


  1. 安全な型定義を持つ新しいインターフェースCallableFunctionを用意し、関数リテラル等の型がそちらに差し変わるという実装になっています。 

387
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
387
Help us understand the problem. What is going on with this article?