既存ReactプロジェクトにTypeScriptを導入した話

[アソビュー Advent Calendar 2019](https://qiita.com/advent-calendar/2019/asoview)の2日目。  

フロントエンドエンジニアの指田です。
今回はアソビューの既存ReactプロジェクトにTypeScriptを導入した内容についてお話します。

## はじめに

Babelでコンパイルしている既存ReactプロジェクトにTypeScriptを導入した話です。
本記事ではTypeScript等の説明は致しません。

## 導入
 
- 既存のビルド構成は変更しない。（[@babel/preset-typescript](https://babeljs.io/docs/en/babel-preset-typescript)を利用。`ts-loader`は使用しない）
- 導入直後はコード変更もしない。
- 完全にTypeScriptにするまでJavaScriptとTypeScriptは混在する。


### インストール

JavaScript Standard Styleを利用しているため、Typescriptのconfigも追加してます。

```sh
# TypeScript
yarn add -D typescript 

# Babel
yarn add -D @babel/preset-typescript

# ESLint
yarn add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-standard-with-typescript
```

### 設定

最初の設定は悩みました。
設定を厳しくしすぎると、開発が進まなくなるので必要そうな設定のみで導入しました。


#### TypeScript

初期は`allowJs`（JavaScriptもトランスパイルに含めるかどうか）は外して導入してます。

```json
{
  "compilerOptions": {
    "skipLibCheck": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "strict": true,
    "noImplicitAny": false,
    "noImplicitThis": true,
    "strictNullChecks": true,
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
  },
}
```

#### ESLint

`overrides`を使用してJavaScriptもTypeScriptも同じ様にlintが効くようにしてます。

```json
{
  "env": {
    "browser": true,
    "jest": true
  },
  "extends": [
    "standard",
    "standard-with-typescript",
    "standard-jsx",
    "plugin:react/recommended",
    "plugin:css-modules/recommended"
  ],
  "plugins": [
    "react-hooks",
    "css-modules"
  ],
  "rules": {
    "react/prop-types": 0,
    "standard/computed-property-even-spacing": "off",
    "react-hooks/rules-of-hooks": "error"
  },
  "parser": "babel-eslint",
  "settings": {
    "react": {
      "version": "detect"
    },
    "node": {
      "tryExtensions": [".ts", ".tsx", ".js"]
    },
  },
  "overrides": [
    {
      "files": ["*.ts", "*.tsx"],
      "plugins": [
        "@typescript-eslint",
        "react-hooks",
        "css-modules"
      ],
      "parser": "@typescript-eslint/parser",
      "parserOptions": {
        "sourceType": "module",
        "project": "./tsconfig.json"
      },
      "rules": {
        "@typescript-eslint/explicit-member-accessibility": "off",
        "@typescript-eslint/explicit-function-return-type": "off",
        "@typescript-eslint/promise-function-async": [
          "error",
          {
            "allowedPromiseNames": [],
            "checkArrowFunctions": false,
            "checkFunctionDeclarations": true,
            "checkFunctionExpressions": false,
            "checkMethodDeclarations": false
          }
        ]
      }
    }
  ]
}
```

#### prettier

```json
{
  "overrides": [
    {
      "files": ["*.ts", "*.tsx"],
      "options": {
        "parser": "typescript"
      }
    }
  ]
}
```

#### npm script

JavaScriptも含めたチェックできるtscコマンドも用意しました。

```json
{
  "scripts": {
    "ts-compile-check": "tsc -p tsconfig.json --noEmit",
    "ts-compile-check-in-js": "tsc -p tsconfig.json --noEmit --allowJs",
  },
}
```

## 型について

導入直後はanyまたは未指定で進めました。  
型を定義していない状態は意味があるのか？とは思いますが、
引数の誤り、不要なPropsがなくなるのでコードがきれいになるメリットがあります。  

## まとめ

- 既存コードがある場合、初期の導入範囲はなるべく小さくすることをおすすめします。
- Babelを使用しているのであれば、[@babel/preset-typescript](https://babeljs.io/docs/en/babel-preset-typescript)を利用する方が早めに導入できると思います。
- ESLint等の設定は`overrides`を利用し、混在しても問題ないようにするのがいいと思います。

## 最後に

アソビューではTypeScriptを導入したプロジェクトはまだ少数なので全てのReactプロジェクトへ導入を検討して行きたいです。