React + TypeScript (+ Electron)でアプリを書き始めるときにやってること

React + TypeScriptでアプリを書くことが多いですが、それを書き始めるセットアップは多種多様な方法があります。
自分がいつもやっているセットアップについてのメモ書きです。

事前準備

create-react-appをグローバルにインストールしておく必要があります。

npm install -g create-react-app

セットアップ手順

react-scripts-tsをインストール

create-react-appを使って、create-react-appのTypeScript版であるreact-scripts-tsをインストールします。
(.で現在ディレクトリに展開出来ます。アプリ名を指定した場合はそのディレクトリが作られます)

create-react-app . --scripts-version=react-scripts-ts

Require all css from index.tsx

index.tsxからすべてのcssファイルをrequireするように変更します。
これにより、.ts, .tsx, .cssのファイルを編集するとHot Reloadingによりすぐにアプリが再描画できます。

diff --git a/src/index.tsx b/src/index.tsx
index 1c66245..bf693b2 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -2,10 +2,16 @@ import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import App from './App';
 import registerServiceWorker from './registerServiceWorker';
-import './index.css';
+
+// import all *.css files
+function requireAll(r: any) {
+    r.keys().forEach(r);
+}
+
+requireAll((require as any).context('./', true, /\.css$/));

index.tsxはwebpackのrequire.contextに依存し汚くなりますが、他のコンポーネントなどの依存はキレイに保つ意図があります。(各コンポーネントがcssをrequireしなくていい)

この変更は、次のパターンでコンポーネントを実装する際に役立ちます。

  • JavaScriptとCSSは独立しているが同じディレクトリに並べる
    • ComponentName.tsx
    • ComponentName.css
  • CSSはSUIT CSSなどのルールに習いコンポーネント名をクラス名にする
    • .コンポーネント名 {}
    • .コンポーネント名-子要素名 {}
    • .コンポーネント名.is-ステート名 {}

詳細は次のドキュメントに書いてあります。

アプリを起動

後はアプリを起動して書き始めるだけです。

npm start

サンプルリポジトリ

実際にこの手順でセットアップしたリポジトリがあります。

git clone https://github.com/azu/react-typescript-startup
cd eact-typescript-startup
yarn install
yarn start

実例

ステート管理はalminを使っていることが多いです。
デフォルトでTypeScriptに対応していて、TypeScriptに依存していないため。


Tips

tslintを無効化する

How to disable tslint? · Issue #133 · wmonk/create-react-app-typescript

tslint.jsonrules fieldを空にすることで無効にできます。

diff --git a/tslint.json b/tslint.json
index eb5d66a..2b86576 100644
--- a/tslint.json
+++ b/tslint.json
@@ -1,99 +1,3 @@
 {
-    "extends": ["tslint-react"],
-    "rules": {
-        "align": [
-            true,
-            "parameters",
-            "arguments",
-            "statements"
-        ],
-        "ban": false,
-        "class-name": true,
-        "comment-format": [
-            true,
-            "check-space"
-        ],
-        "curly": true,
-        "eofline": false,
-        "forin": true,
-        "indent": [ true, "spaces" ],
-        "interface-name": [true, "never-prefix"],
-        "jsdoc-format": true,
-        "jsx-no-lambda": false,
-        "jsx-no-multiline-js": false,
-        "label-position": true,
-        "max-line-length": [ true, 120 ],
-        "member-ordering": [
-            true,
-            "public-before-private",
-            "static-before-instance",
-            "variables-before-functions"
-        ],
-        "no-any": true,
-        "no-arg": true,
-        "no-bitwise": true,
-        "no-console": [
-            true,
-            "log",
-            "error",
-            "debug",
-            "info",
-            "time",
-            "timeEnd",
-            "trace"
-        ],
-        "no-consecutive-blank-lines": true,
-        "no-construct": true,
-        "no-debugger": true,
-        "no-duplicate-variable": true,
-        "no-empty": true,
-        "no-eval": true,
-        "no-shadowed-variable": true,
-        "no-string-literal": true,
-        "no-switch-case-fall-through": true,
-        "no-trailing-whitespace": false,
-        "no-unused-expression": true,
-        "no-use-before-declare": true,
-        "one-line": [
-            true,
-            "check-catch",
-            "check-else",
-            "check-open-brace",
-            "check-whitespace"
-        ],
-        "quotemark": [true, "single", "jsx-double"],
-        "radix": true,
-        "semicolon": [true, "always"],
-        "switch-default": true,
-
-        "trailing-comma": [false],
-
-        "triple-equals": [ true, "allow-null-check" ],
-        "typedef": [
-            true,
-            "parameter",
-            "property-declaration"
-        ],
-        "typedef-whitespace": [
-            true,
-            {
-                "call-signature": "nospace",
-                "index-signature": "nospace",
-                "parameter": "nospace",
-                "property-declaration": "nospace",
-                "variable-declaration": "nospace"
-            }
-        ],
-        "variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore", "allow-pascal-case"],
-        "whitespace": [
-            true,
-            "check-branch",
-            "check-decl",
-            "check-module",
-            "check-operator",
-            "check-separator",
-            "check-type",
-            "check-typecast"
-        ]
-    }
+    "rules": {}
 }

Electronアプリの場合

electron-webpackを使うことでreact-scripts-tsと大体同じような構成を取れます。
electron-webpack-tsのAddonを入れることでTypeScriptの対応が入るので、後は大体同じです。(webpackの面倒なところをスキップできます。)

実例: