React Native+TypeScriptの環境構築

React Nativeに実際にチャレンジした時の忘備録として残しておく。
ReactNativeを始める際には、Expoとreact-native-cliの二択がまず真っ先に浮かぶと思いますが、後者でやったのでその記録です。ただ、今から始めるならまずはExpoのことを調べてみるのをお勧めします。

今回この記事では
- ReactNativeにTypeScriptを導入する
- ReactNativeでローカルの画像を使用できるようにしておく
この2つを行います。

# 前提条件
* yarnをインストール済み
* VSCodeを利用(`code`コマンドでterminalから起動できるようにしておく)
* react-nativeのバージョンは0.59.0
* macOS mojave(10.14.4)
* 2019年4月時点での情報です。

# プロジェクトの作成
まずyarnを用いて`react-natiev-cli`を使えるようにします。

```terminal
$ yarn global add react-native-cli
```

そしてプロジェクト作成。プロジェクト名にハイフンなどは使えないため注意。

```terminal
$ react-native init <プロジェクト名>
$ cd <プロジェクト名>
```

typescript関連の依存環境を構築。
reactとreact-native本体には型定義が含まれていないので、型定義ファイルも追加。

```console 
$ yarn add --dev typescript
$ yarn add --dev react-native-typescript-transformer
$ yarn add --dev @types/react @types/react-native
```


typescriptの設定ファイルのtsconfigを作成

```console
$ yarn tsc --init --pretty --jsx react
```
以下のような内容のtsconfig.jsonが生成される。
多分実際はもっと大量のコメントアウト行だらけなので、ここではコメントアウトされていない行のみを抜粋。

```tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "jsx": "react", 
    "strict": true, 
    "esModuleInterop": true
  }
}
```

そのうち、以下の項目のコメントアウトを外す。

```json
{
  ...
   "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
  ...
}
```

さらにcompilerOptionsをnode_modulesでは対象外にする。`,`の追加を忘れずに。

```diff:tsconfig.json
{
  "compilerOptions": {
    ...
+  },
+ "exclude": [
+   "node_modules"
+ ]
}
```

ここでtsconfig.jsonを自分好みに書き換えると良い。
`StrictNllCheck`をオンにしたりなど...

ソースコードをroot直下に起き続けるのは気が進まないので、
階層を1つ下げるためにsrcフォルダを作成する。

```terminal
$ mkdir src
$ code ./
```
VSCodeが開いたら、`App.js`を`App.tsx`にリネームし、`./src`配下に移動する。
するとVScodeによって自動でimportの更新をしていいか聞かれるのでYesを選択。
もし更新するかのダイアログが出なかった場合なども下記の`VSCodeを使わずに更新する場合`を参照

>**VScodeを使わずに更新する場合**
>srcフォルダを作成し、そこにApp.jsを移動。

>```terminal
$ mkdir src
$ mv App.js src/
```

>index.jsを下記の様に編集する

>```diff:index.js
+ import App from "./src/App";
- import App from "./App";
```


`rn-cli.config.js`というファイルを作成する。

```console
$ touch rn-cli.config.js
```
このファイルの内容は次の通りにする

```rn-cli.config.js
module.exports = {
  getTransformModulePath() {
    return require.resolve("react-native-typescript-transformer");
  },
  getSourceExts() {
    return ["ts", "tsx"];
  }
};

```

`package.json`を開いて、`script`内に`run-ios`と`run-android`を追加することで、いちいち`react-native run-ios`と打たずに`yarn run-ios`で動かせるようにする。

```diff:package.json
{
  "name": "tsSampleTest",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start",
    "test": "jest",
+   "run-ios": "react-native run-ios",
+   "run-android": "react-native run-android"
  },
  "dependencies": {
    "react": "16.6.3",
    "react-native": "0.58.6"
  },
  "devDependencies": {
    "@types/react": "^16.8.7",
    "@types/react-native": "^0.57.38",
    "babel-core": "^7.0.0-bridge.0",
    "babel-jest": "24.3.1",
    "jest": "24.3.1",
    "metro-react-native-babel-preset": "0.53.0",
    "react-native-typescript-transformer": "^1.2.12",
    "react-test-renderer": "16.6.3",
    "typescript": "^3.3.3333"
  },
  "jest": {
    "preset": "react-native"
  }
}
```

以下のコマンドを叩いて動作確認しておしまい。

```terminal
$ yarn run-ios
```

`No Bundle Url Present`みたいなエラーが表示されてしまったときは、
まずはシミュレーター画面内下部のReloadを押す(またはSimulator上でCommand+R)をしてみる。
それでもダメな場合xcodeprojからXcode開いて直接実行させちゃうのが自分の環境では効果的でした。
そのあとXcode止めて、もう一度コマンド叩けば高確率で動く。


## tslint、prettierの導入
書き方をチーム内で統一させるためのツール。複数人でやるなら入れておきたい。
いらないと思う場合はこの章はスキップしていいです。
自分の場合はインデント破壊されたコードが飛んでくることがあったので全員に導入。

```terminal
yarn add --dev prettier 
yarn add --dev tslint tslint-react
yarn add prettier --dev tslint-config-prettier  tslint-plugin-prettier
```
tslintとprettierが競合したときprettierを優先する。
prettierとtslintの設定を記述する。

```.prettierrc
{
    "printWidth": 120, 
    "trailingComma": "all",
    "singleQuote": true,
    "jsxBracketSameLine": false
}
```
`printWidth`は1つの行での最大文字数,
`trailingComma`は、配列や辞書での末尾カンマ強制,
`singleQuote`は、シングルクオーテーション,
`jsxBracketSameLine`は、jsxの開始タグでの改行をするように

```tslint.json
{
  "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
  "rules": {
    "jsx-no-lambda": false, // JSX(TSX)内でのラムダ式
    "member-access": false,  // アクセス権の表記なし
    "interface-name": false,  // InterFace名の前に"I"をつけることを強制しない
    "prefer-for-of": false,  // for(i=0;i<9;i++)の代わりにfor-of構文を強制しない
    "ordered-imports": false, // importをアルファベット順にしなくても良い
    "object-literal-sort-keys": false, // Object(Dictionary)の順序をアルファベットに強制しない
    "no-console": false,  // console.log()を禁止しない
  },
  "linterOptions": {
    "exclude": [
      "config/**/*.js",
      "node_modules/**/*.ts",
      "coverage/lcov-report/*.js"
    ]
  }
}
```

これらの設定は好きなように変えればいいと思います。

## imageを読み込んでも怒られないようにする
Imageタグでローカルの画像を読み込む時には下記2つのどちらかを使うことになるかと思います。
他のコンポーネント読み込むときも上部にimportを書くので、同じように確認すれば済むように`import`を使う方が個人的に好きです。

```typescript
import img from './resources/image/hoge.png'
<Image source={img} />

// または
<Image source={require('./resources/image/hoge.png')} />
```

で、これを使えるようにするには、まず型定義ファイル`images.d.ts`をプロジェクト直下(`index.js`などと同じ場所)に作成して、中身を次の通りに編集する。追加したい拡張子を記載すれば良い。

```images.d.ts
declare module '*.svg'
declare module '*.png'
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.gif'
declare module '*.bmp'
declare module '*.tiff'
```
それに加えて、`tsconfig.json`を編集する。

```tsconfig.json
    "typeRoots": ["./images", "node_modules/@types"], 
```
こうするとimportで画像を読み込んで表示することができるようになります。


# まとめ
これで自分的には色々試せる環境になったかなと。
react-native-cliだと自分が何をやったか覚えておけばプロジェクトの中身はつかみやすいですが、ライブラリの追加時にやることが多く、ある程度ネイティブの環境への理解がないと辛いことがちらほらあるかもしれないなと感じました。
それに対してExpoは一部のライブラリが対応していないことがネックであるとよく言われます。
しかし、記事作成時点で自分が使おうとしたライブラリはExpoに対応しているのが多かったように見受けられました(厳密に検証したりしたわけではありませんが)。
自分の環境下ではExpoで作ったTypescript環境だと、ejectすると動かなくなる状況に見舞われて、解決にかける時間がちょっと微妙だったこともありcliで挑戦しました。


# 参考URL
めちゃくちゃ頼りました
[TypeScriptでReact Native開発をする方法2018年最新版 ](https://bagelee.com/programming/react-native/typescript-react-native-2018/)