TypeScriptで型定義(DefinitelyTyped)を提供する方法!外部ライブラリのベストプラクティス
生徒
「TypeScriptを使っていて、自分が作った便利な道具を他の人にも使ってもらいたいのですが、型をどうやって渡せばいいのか分かりません。」
先生
「それは素晴らしいですね!自分が作ったライブラリに型情報を付けることで、使う人はエラーをすぐに見つけることができて、とても便利になります。そのための仕組みとしてDefinitelyTypedという世界共通の型置き場や、ライブラリそのものに型を含める方法があるんですよ。」
生徒
「具体的には、どちらの方法がおすすめで、どうやって書けばいいんですか?」
先生
「現在はライブラリ自体に型を含める方法が主流ですが、歴史のある仕組みも知っておくと役立ちます。初心者の方でも迷わないように、ベストプラクティスを順番に解説していきますね!」
1. そもそもTypeScriptの型定義とは?
プログラミングの世界では、自分が書いた便利なコードの塊をライブラリと呼びます。このライブラリを他の人が使うとき、TypeScriptではその道具が数字を扱うのか、文字を扱うのかという情報が必要になります。これが型定義です。もし型がないと、プログラムを動かすまで間違いに気づけず、まるで説明書のない複雑なパズルを組み立てるような状態になってしまいます。
ライブラリという言葉を、料理の調味料セットに例えてみましょう。砂糖だと思って使ったのに、実は塩だったという間違いが起きないように、あらかじめ中身を宣言しておくのが型定義の役割です。この宣言があるおかげで、パソコンの画面上で入力中にヒントが表示され、間違った使い方をするとすぐに赤線で教えてくれるようになります。
2. 第三者ライブラリに型を提供する方法の種類
ライブラリに型を提供する方法は、大きく分けて二つのパターンがあります。一つ目は、自分が作ったライブラリの中に型情報を同梱して一緒に配布する方法です。現在はこの方法が最も推奨されており、使う側が特別な設定をしなくてもすぐに型が反映されるため、とても親切な設計と言えます。
二つ目は、歴史的な背景から生まれたDefinitelyTypedと呼ばれる巨大な倉庫に、型情報だけを預ける方法です。これは、JavaScriptで作られた古いライブラリなど、本人に型の知識がない場合に、有志の人たちが外付けの説明書として型を提供するために使われます。初心者の皆さんが新しくライブラリを作る場合は、まずは一つ目の同梱する方法を目指すのが一番確実な道です。
3. ベストプラクティスその1:ライブラリに型を同梱する
最も理想的な方法は、作成したプログラムの中に型定義ファイルを含めてしまうことです。これは、お菓子を買った時に、箱の中に栄養成分表やアレルギー表示が既に入っているようなイメージです。利用者は追加で何も買う必要がありません。具体的には、ファイルの拡張子を、プログラム本体の最後につけることで表現します。
まずは、非常にシンプルな足し算の道具を作ってみましょう。ここでは、数値を二つ受け取って合計を返すという約束事を型で定義します。このように、あらかじめ形を決めておくことで、間違って文字を渡してしまった時にエラーが出るようになります。
// 道具の形(型)を決める
export type MathFunction = (a: number, b: number) => number;
// 実際に動く中身を作る
export const add: MathFunction = (a, b) => {
return a + b;
};
このコードでは、二つの数字を足すというルールを定義しています。このように型をエクスポート(公開)することで、他のファイルでも同じルールを使えるようになります。
4. 設定ファイルでの型指定のやり方
ライブラリを作るときには、どこに説明書があるかをパソコンに教える必要があります。それには、設定ファイルであるパッケージドットジェイソンというファイルを使います。このファイルは、ライブラリの自己紹介カードのようなものです。ここに「型定義はこのファイルに書いてあるよ」と一言添えるだけで、世界中の人がその型を利用できるようになります。
具体的には、タイプスという項目を追加します。これを行わないと、どれほど丁寧に型を書いたとしても、使う側のパソコンはそれを認識してくれません。まるで宝の地図があっても、その場所が書いていないのと同じ状態になってしまいます。初心者が最も忘れがちなポイントなので注意しましょう。
{
"name": "my-simple-library",
"version": "1.0.0",
"main": "index.js",
"types": "index.d.ts"
}
このように設定することで、利用者がこのライブラリをインストールした瞬間に、TypeScriptが自動的に型を読み込んでくれるようになります。
5. DefinitelyTypedとは何かを知っておこう
次に、外部の巨大な型倉庫であるDefinitelyTypedについて解説します。ここは、世界中のプログラマーが協力して、型がないライブラリに対して型を提供するための場所です。もしあなたが、すでに有名なJavaScriptライブラリを使っていて、それに型がなくて困っている場合は、ここから型をダウンロードしてくることになります。
ここで管理されている型は、アットタイプスという名前で公開されます。例えば、ウェブサイトを作る時によく使われる有名なライブラリには、ほとんどこの外付けの型が存在します。自分がライブラリを作る立場になったとき、どうしても本体に型を含められない事情がある場合のみ、この巨大倉庫への登録を検討することになります。しかし、手続きが少し複雑なので、まずは自分のライブラリの中に書く練習をしましょう。
6. 型定義ファイルを生成する自動化のコツ
型を一つずつ手書きするのは大変な作業です。しかし、TypeScriptには、プログラム本体から自動的に説明書となる型ファイルを生成してくれる機能があります。これを使えば、普段通りにプログラムを書くだけで、配布用の型ファイルが勝手に出力されます。これを宣言ファイル生成と呼びます。
設定ファイルで宣言フラグをオンにすると、コンパイル(変換作業)の際に、拡張子がドットディー、ドットティーエスという特別なファイルが出来上がります。これが、ライブラリの利用者にとってのガイドブックになります。中身は関数の名前や引数の種類だけで、具体的な処理内容は隠されています。これにより、ライブラリの使い勝手が向上しつつ、中身の複雑さを隠すことができるのです。
// 設定ファイルで declaration を true にすると
// 以下のような型定義ファイルが自動で作られます
/** ユーザーの情報を表す型 */
export interface User {
id: number;
name: string;
isActive: boolean;
}
/** 指定したIDのユーザーを取得する関数 */
export declare function getUser(id: number): User;
この自動生成されたファイルがあるおかげで、他の人は関数の使い方を一目で理解できるようになります。
7. インターフェースを使って拡張性を高める
型を提供するときのコツとして、インターフェースという仕組みを使うことが推奨されます。インターフェースとは、物の見た目や最低限守るべきルールのリストのようなものです。これを使うと、ライブラリを使う人が自分の好みに合わせて型を少しだけカスタマイズしたり、付け加えたりすることがしやすくなります。
例えば、設定情報を扱う型を作る場合、インターフェースを使っておけば、後から「新しい設定項目を増やしたい」という要望にも柔軟に応えることができます。型をガチガチに固めすぎず、少しの遊び心と拡張性を持たせておくことが、優れたライブラリ作者への第一歩です。相手の立場に立って、どうすれば使いやすいかを考えることが大切です。
// 設定オプションのインターフェース
export interface AppConfig {
title: string;
showNotifications?: boolean; // ?をつけるとあってもなくても良い設定になる
theme: "light" | "dark"; // どちらかの文字しか受け付けない設定
}
// この設定を使う関数
export function initializeApp(config: AppConfig): void {
console.log(config.title + "を起動します");
}
実行結果のイメージは以下のようになります。
MyAppを起動します
8. 依存関係にあるライブラリの型はどう扱うべきか
自分のライブラリが、別の誰かのライブラリを使っている場合は注意が必要です。これを依存関係と呼びます。もし自分のライブラリの型定義の中で、別のライブラリの型を使っている場合、それもセットで相手に渡るようにしなければなりません。これを忘れると、利用者の元で「型が見つかりません」というエラーが出てしまいます。
これを防ぐためには、型情報をどのカテゴリに入れて配布するかを正しく選ぶ必要があります。開発中だけに使う型なのか、実際に使う人にも必要な型なのかを見極めることが重要です。初心者のうちは難しく感じるかもしれませんが、まずは「自分が使っている外部の型も、相手に届く必要があるかどうか」を自問自答してみてください。基本的には、自分が公開する関数の一部にその型が含まれているなら、それは必須の型となります。
9. 型定義をテストして品質を保つ大切さ
最後に、作った型が本当に正しく動くかを確認する作業も欠かせません。プログラムが正しく動いても、型定義が間違っていると、利用者は混乱してしまいます。これを防ぐために、あえて間違った使い方をしてみて、ちゃんとエラーが出るかどうかを確認するテスト手法があります。
型は一度提供すると、後から変更するのが大変な場合もあります。多くの人があなたのライブラリを使い始めた後で型を変えてしまうと、みんなのプログラムが壊れてしまうからです。そのため、最初の段階で丁寧に型を設計し、テストしておくことが、信頼される開発者になるための近道です。パソコンを初めて触るような方でも、一つずつ丁寧に型を付けていくことで、確実にプログラミングの腕は上がっていきます。焦らずに取り組んでいきましょう。