はじめに
この記事はこのような方を対象にしてます。
- TSは普段使っているけどd.tsファイルの読み方がいまいちわからない。
- d.tsファイルはなんとなく読み書きできるけど記述するときにつまづく時がある。
d.tsファイル、意外とややこしい
「意外とややこしい」というのは個人の所感にはなりますが、そう思っている人もきっといるはず。。。
d.tsファイルは端的にいうと、明示的に型付されていないJSのモジュールに対して、型をつけてTSでハッピーになるためのファイルです。
実際にd.tsファイルに触れる機会といえば、TSのプロジェクトでIDEやエディタから定義ジャンプして型を確認したり、ライブラリ作者やコミッターであれば、d.tsファイルを生成したり、記述したりすることがあるでしょう。
しかし、実際にd.tsファイルを記述する機会は人によっては少なくい場合も多く、自分自身d.ts特有のキーワードや記述に困惑することもよくあったので、備忘録も兼ねてこの記事にまとめてみた。
d.tsファイルを読み書きする前に
まず、d.tsファイルを読み書きするのに、前提として重要なポイントがあります。
ファイルがモジュールなのかスクリプトなのか
この前提があることで、d.tsのルールがより理解できるでしょう。
モジュールとは
意味合いとしてはJSのモジュールシステムとほぼ同じです。TypeScriptのコンパイラはexport
orimport
キーワードを見つけると、そのファイルはモジュールファイルであると認識します。モジュールのコードはどこからでもアクセスできるものではなく、明示的にexport
し、使用するファイルからimport
してやる必要があります。
スクリプトとは
例えば、jQueryを使用したことがる人はこのような記述をしたことがあるのではないでしょうか。
<script src="/path/to/jquery.min.js"></script>
<script src="/path/to/main.js"></script>
$(function () {
const $element = $('.js-target-element');
// do something...
});
このコードスニペットでは、$
のjQueryオブジェクトがimport
されることなく、参照されているが、これはつまり、jquery.min.js
を実行したことで、window
オブジェクトからjQueryオブジェクトが生えた状態と言えるわけです。ちなみに、TSの世界ではUMDモジュールという名前がよく出てくるが、window
オブジェクトにモジュールを生やすのと、exports
オブジェクトにモジュールを代入するCommonJSを1ファイルで兼用することができる仕組みのことです。UMDモジュールではCommonJS環境であればexports
オブジェクトに、ブラウザ環境ではwindow
オブジェクトにモジュールを生やします。
このモジュールとスクリプトという2つの世界観に分けてd.tsについて書いていく。
モジュールのd.ts
基本形
前述した通り、モジュールにはexport
、import
キーワードが必要す。例えば、以下のようなJSモジュールを想定する。
export function sum(num1, num2) {
return num1 + num2
}
export function sayHello(name) {
console.log(`Hello, ${name}!`)
}
d.tsは以下
export function sum(num1: number, num2: number): number;
export function sayHello(name: string): void;
ここまでは想像に難くないはず。
declare global
ライブラリの型定義でよく見かけるdeclare global
から始まる記述について。
その名の通り、グローバルに型定義をするが、モジュール内でのみ使用可能。スクリプトd.tsではdeclare global
を記述できません。例えば、下記のコードを考えてみましょう。
window.hoge = hoge;
export function hoge() {
//
}
import './mod';
window.hoge()
例に挙げたmod.js
の型定義ファイルmod.d.ts
を記述するとしたら下記。
declare global {
interface Window {
hoge: typeof hoge
}
}
export function hoge(): void
export as namespace
export as namespace
。こちらもグローバルスコープからアクセスできるものを定義するキーワードだが、モジュールd.tsでのみ使用可能。これによってグローバルスコープから定義にアクセスできる。どれにアクセスできるかは、参照しているファイルがモジュールかスクリプトかによって異なってくる。
型定義
export function hoge(args: Hoge.Args): void
interface Args {
foo: string;
bar: boolean;
}
export as namespace Hoge
モジュールからは型定義にのみアクセスできる。
import { hoge } from './mod';
const hogeArgs: Hoge.Args = {
foo: '...',
bar: false,
};
hoge(hogeArgs);
スクリプトからは型定義以外にもアクセスできる。
const hogeArgs: Hoge.Args = {
foo: '...',
bar: false,
};
Hoge.hoge(hogeArgs);
スクリプトのd.ts
スクリプトのd.tsの記述は比較的シンプルです。export
とimport
を含まないファイルのこと。
サードパーティースクリプトやアナリティクスのコードに対して定義することが多い。TypeScriptコンパイラにグローバルからアクセスできるものとして宣言する。
const dataLayer: unkwon[];
function doSomething(): void;
interface Buzz {
a: string;
b: string;
}
function handleClick() {
dataLayer.push({ event: '...' });
doSomething();
}
const buzz: Buzz = { a: '...', b: '...' };
尚、グローバルスコープのinterface
を拡張する場合、モジュールと違ってdeclare global
の記述は不要。
interface Window {
hoge(): void
}
window.hoge();
既存のモジュールの型を拡張
既存のモジュールのinterface
に新しいプロパティを追加したいとなったとする。(Module Augmentationという)例えば元のモジュールの型定義がこちらだった場合、
export interface Hoge {
a: string;
b: string;
}
export interface Foo {
a: number;
b: number;
}
これはモジュールのimport
先のファイルで意図通りにモジュールの型定義を上書きできない。
declare module 'original' {
interface Hoge {
c: string
}
}
この例ではスクリプトと認識されるため、Module Augmentationではなく、Ambient Moduleになる。
declare module 'original' {
interface Hoge {
c: string
}
}
export {} // 追加
export
キーワードを追加して、TypeScriptコンパイラにモジュールと認識させることが必要。
最後に
別のプロジェクトではエラーにならなかったのに、今のプロジェクトで同じ型定義をしようとしたらなぜかエラーになったというときなどに、この記事がヒントになれば幸いです。
もっと詳しく知りたい場合は公式のリファレンスを参照してください。