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


[React](https://reactjs.org/) + [TypeScript](http://typescriptlang.org/)でアプリを書くことが多いですが、それを書き始めるセットアップは多種多様な方法があります。
自分がいつもやっているセットアップについてのメモ書きです。

## 事前準備

[create-react-app](https://github.com/facebookincubator/create-react-app "create-react-app")をグローバルにインストールしておく必要があります。

    npm install -g create-react-app

## セットアップ手順

### [react-scripts-ts](https://github.com/wmonk/create-react-app-typescript "react-scripts-ts")をインストール

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

```shell-session
create-react-app . --scripts-version=react-scripts-ts
```

### Require all css from `index.tsx`

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

```diff
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](https://webpack.js.org/guides/dependency-management/ "require.context")に依存し汚くなりますが、他のコンポーネントなどの依存はキレイに保つ意図があります。(各コンポーネントがcssをrequireしなくていい)

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

- JavaScriptとCSSは独立しているが同じディレクトリに並べる
	- `ComponentName.tsx`
	- `ComponentName.css`
- CSSは[SUIT CSS](http://suitcss.github.io/ "SUIT CSS")などのルールに習いコンポーネント名をクラス名にする
	- `.コンポーネント名 {}`
	- `.コンポーネント名-子要素名 {}`
	- `.コンポーネント名.is-ステート名 {}`

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

- [azu/large-scale-javascript: 複雑なJavaScriptアプリケーションを作るために考えること](https://github.com/azu/large-scale-javascript "azu/large-scale-javascript: 複雑なJavaScriptアプリケーションを作るために考えること")
- [CSSの読み込みについて](https://github.com/azu/large-scale-javascript/blob/master/docs/css.md#css%E3%81%AE%E8%AA%AD%E3%81%BF%E8%BE%BC%E3%81%BF%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6 "CSSの読み込みについて")

## アプリを起動

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

```shell-session
npm start
```

## サンプルリポジトリ

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

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

- [azu/react-typescript-startup: The steps to create app using React + TypeScript.](https://github.com/azu/react-typescript-startup "azu/react-typescript-startup: The steps to create app using React + TypeScript.")


### 実例

- [azu/faao: Faao is a GitHub issue/pull-request client on Electron.](https://github.com/azu/faao)
- [azu/irodr: RSS reader client like LDR for Inoreader.](https://github.com/azu/irodr)
- [proofdict/editor: Proofdict editor.](https://github.com/proofdict/editor)

ステート管理は[almin](https://github.com/almin/almin "almin")を使っていることが多いです。
デフォルトでTypeScriptに対応していて、TypeScriptに依存していないため。

- ドメインとか作らずにStoreだけで書くとクラスベースのReduxのReducerみたいになる
- <https://github.com/azu/searchive/blob/master/packages/searchive-app/src/renderer/store/SearchIndexStore/SearchIndexStore.ts>

----

## Tips

### tslintを無効化する

> [How to disable tslint? · Issue #133 · wmonk/create-react-app-typescript](https://github.com/wmonk/create-react-app-typescript/issues/133 "How to disable tslint? · Issue #133 · wmonk/create-react-app-typescript")

`tslint.json`の `rules` fieldを空にすることで無効にできます。

```diff
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](https://github.com/electron-userland/electron-webpack/ "electron-webpack")を使うことで[react-scripts-ts](https://github.com/wmonk/create-react-app-typescript "react-scripts-ts")と大体同じような構成を取れます。
`electron-webpack-ts`のAddonを入れることでTypeScriptの対応が入るので、後は大体同じです。(webpackの面倒なところをスキップできます。)

- [Add-ons · electron-webpack](https://webpack.electron.build/add-ons "Add-ons · electron-webpack")

実例:

- [searchive/packages/searchive-app at master · azu/searchive](https://github.com/azu/searchive/tree/master/packages/searchive-app "searchive/packages/searchive-app at master · azu/searchive")