ReactをTypeScriptで書く2: 設定、d.ts編

メモです。
そして前回からの続きです。

コンパイラーの設定

コンパイルの設定は、ルートフォルダの"tsconfig.json"で行われている。

tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve"
  },
  "include": [
    "src"
  ]
}

"compilerOptions"がコンパイラーの設定になる。
https://www.typescriptlang.org/docs/handbook/compiler-options.html

TSLint、Prettierの設定

コンパイラーの設定の他に、TSLintやPrettierの設定もできる。
まず、ライブラリをインストールする。

yarn add -D tslint tslint-react tslint-config-prettier tslint-plugin-prettier

設定は、tslint.jsonを作成することで行うことができる。

tslint.json
{
  "rulesDirectory": [
    "tslint-plugin-prettier"
  ],
  "extends": [
    "tslint:recommended",
    "tslint-react",
    "tslint-config-prettier"
  ],
  "linterOptions": {
    "exclude": [
      "config/**/*.js",
      "node_modules/**/*.ts",
      "coverage/lcov-report/*.js"
    ]
  },
  "rules": {
    "prettier": true
  }
}

また、prettierの設定は、.prettierrcで行う。

.prettierrc
{
  "singleQuote": true,
  "trailingComma": "es5"
}

prettierの設定、VSCodeでの設定は下記リンクを参照。
TypeScriptプロジェクトにコードフォーマッタPrettierを導入する - Qiita

初期設定だと色々とルールがきつい。
参考になるサイトやボイラープレートを探す必要がある。
TSLint core rules
TSLint v5.7.0 で指定できる全 rules をまとめた - Corredor

型定義ファイル

次に、内部にあるreact-app.d.tsを確認してみる。
中はこんな感じになっている。

react-app.d.ts
/// <reference types="node" />
/// <reference types="react" />
/// <reference types="react-dom" />

declare namespace NodeJS {
  interface ProcessEnv {
    NODE_ENV: 'development' | 'production' | 'test'
    PUBLIC_URL: string
  }
}

declare module '*.bmp' {
  const src: string;
  export default src;
}

/* 省略 */

このように、末尾の拡張子が".d.ts"になっていて、ひたすらdeclareとか書いてあるファイルが存在する。

JavaScriptで書かれたライブラリには型情報が存在しないため、TypeScriptでは暗黙のany型となり、tsconfig.jsonでnoImplicitAnyを設定しているとエラーとなる。
そこで、上記のようなファイルを用意し、型定義を行うことができる。
そのようなファイルを型定義(型宣言)ファイルという。

簡単な例として、画像ファイル部分の定義を見てみる。

declare module '*.bmp' {
  const src: string;
  export default src;
}

例えば、bpmファイルは外部モジュールにあたり、TypeScriptで定義されていない。
そのため、.bmp(や他の画像ファイル群)をmodule(モジュール)として、declareというワードで宣言して、TypeScriptで通るようにしている。

型定義ファイルの場所

一部のライブラリには、型定義ファイルが用意されている。
なかったとしても、"DefinitelyTyped"というプロジェクトがあって、有志の作成した型定義ファイルをインストールすることができる。
対応ライブラリが結構あって、メジャーなものは一通り揃っている。
https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types
(多すぎるので全部表示されません)

DefinitelyTypedにある型定義ファイルは、yarn add -D @types/ライブラリ名のような形式でインストールすることができる。
型定義ファイルがあるかは、TypeSearchで確認できる。
メンテがなくて更新されていなかったり、バージョンが合ってなかったりすることもある。

型定義ファイルで見るやつ

declare

declareは、他の型定義ファイルでも見ることがある。
declareは「宣言する」「表明する」を意味する。

先述したように、JavaScriptの変数や関数には型情報がない。
declareでは、それらに型を定義することができる。
例えば、ライブラリでdisplayNameという関数が使われているなら…。

// declareで、displayNameという関数の型情報を宣言
declare function displayName(name: string): void;

こうすることで、displayNameという関数は、stringの引数nameを持って、返り値を持たない関数であることをTypeScriptに教えることができる。

declare自体はTypeScript特有の機能で、コンパイル時に出力されない。

namespace

一部の型定義ファイルには、namespaceというのもある。
これはその名の通り名前空間を意味する。

かつて内部moduleと呼ばれていたのが、ES6で正式にmoduleが定められたため、namespaceとして明確に区別するようになったらしい。

仮に、Fooというモジュールがインストールされているとする。
下記のように、arrayFilter、displayName、helloWorldという関数を持つ。

node_modules/foo/index.js
// Fooに、3種類の関数がある
const Foo = {
  arrayFilter: function(arr, target) {
    return arr.filter(item => item.includes(target))
  },
  displayName: function(name) {
    console.log(name);
  },
  helloWorld: function() {
    console.log('hello world');
  }
};

export default Foo;

これらの関数に型を付与したい。
型定義ファイルではこのように、namespaceを設定する。

node_modules/@types/foo/index.d.ts
//namespace Fooを宣言
declare namespace Foo {
  export function arrayFilter(arr: any, target: string): any;
  export function displayName(name: string): void;
  export function helloWorld(): void;
}

export default Foo;

これで、JavaScriptファイルだったfooモジュールに型情報が追加される。

hoge.ts
import Foo from 'foo'; // declare namespace Fooで型情報が宣言されている

// Foo.arrayFilter
const arr: string[] = ['hoge', 'fuga', 'piyo'];
console.log(Foo.arrayFilter(arr, 123)); // 第二引数がnumberなのでエラー

Triple-slash directives

react-app.d.tsの冒頭にあった

react-app.d.ts
/// <reference types="node" />
/// <reference types="react" />
/// <reference types="react-dom" />

/* 省略 */

みたいなのはTriple-slash directivesと言われる。

<reference path="..." />のように指定して、他の型定義ファイルを参照する。
上の<reference types="..." />では、@typesの型定義ファイルを参照している。

(その他、型定義ファイルの作成については、こちらが詳しいです。)
TypeScript の型定義ファイルと仲良くなろう - Hatena Developer Blog

まとめ

脱線しましたがReact編に続きます。