🍃このブログは移転しました。
3秒後、自動的に移動します・・・。

React x MobXな趣味プロダクトをTypeScriptでリライトしようとした

React x MobXでできてる自分専用の音楽ストリーミングサービスがありまして。

冬休みなのでTypeScriptで書き直したりしてみようかなと思ってちまちまやってた。
ただ結局は自分一人しかコード書かないので、コスパに見合わないと判断して採用は見送った。

https://github.com/leader22/mmss-client/tree/topic/ts

まあその過程で色々学びはあったので、忘れないようにメモっとく。

環境構築編

Webpackでコンパイル

なにはともあれ開発環境を。
元がBabel x Webpackだったので、そこをまず変える。

npm install -D typescript awesome-typescript-loader

そして`babel-loader`と差し替えるだけ。
TSはReactのJSXにも対応してるので、特に困る点はない。

ただWebpackのTypeScriptのLoaderで調べると2つ出てくるのだけ悩んだ。

Awesomeの方がチューニングしてあって速いぜ!って書いてあるけど、この程度のプロジェクトじゃたぶん大した差はでない気がする。

`tsconfig.json`は適当に見繕って用意すれば良い。
`tsc --init`で生成されるファイルがValid JSONじゃなくてちょっとアレだった。

今回は割と堅めな設定にしてみたので、気になる方は冒頭のリポジトリへのリンクからどうぞ。

TypeScriptのLint

お次はLint。
またこれも調べると選択肢が2つ出てくる・・。

TSLintを使うか、ESLint w/ TSサポートにするか。

ESLintを使う場合のメリットとしては、普段使いの`eslintrc`がそのまま使えて設定ファイルを適当にできるかと思ったから。
けど実際は、`no-undef`みたいなルールがTSのクラスの型定義でコケたりと、微妙に惜しい。

class Foo {
  // これがESLintにunused!っていわれる
  bar: string;
}

なので、設定ファイルをまた育てることにはなるけども、TSLintのほうが良いかなと。
こんなところに時間を割くべきではない。

TypeScriptでハマったところ

型推論してくれない

const opts = {
  method: 'POST',
  credentials: 'same-origin',
};

fetch('/api', opts); // optsが型エラー

コレにハマった。
`fetch(2)`の第二引数は`RequestInit`なので、プロパティだけ見るとこの`opts`は正しいんやけども、実際の型としては`Object`なのでエラーになる。

なので型を明示する。

const opts: RequestInit = {
  method: 'POST',
  credentials: 'same-origin',
};

fetch('/api', opts);

プリミティブでない変数には、すべからく型を明示しておこう・・という学び。
あと関数の引数と返り値についても型は明示しておいて損ない感じ。

型のインポート

Flowでいう`import type`なんてものはなくて、すべて`import`で読み込む。
コード内で使ってない場合、コンパイル時にいい感じにしてくれるらしい。

あとnpmから`@types/xxx`でインストールしてきたものは、自動的に型が読み込まれてエクスポートされてる状態になる。

React x MobXでハマったところ

`.tsx`

JSXを返すファイルを`.tsx`にするのではなく、コード内にJSXを書いたらそれは`.tsx`にする必要がある。

SFCのPropsの型

import * as React from 'react';

interface FooProps {
  count: number;
}

const FooSFC: React.SFC<FooProps> = ({ count }) => (
  <div>Foo: {count}</div>
);

export default FooSFC;

てな感じで指定する。

`inject()`したPropsが見えない

ReactDOM.render(
  <Provider event={event}>
    <EntryApp store={store} />
  </Provider>,
  document.getElementById('root')
);

このように`EntryApp`に直接渡してるのは`store`だけで、`event`はContext経由になる場合。
Issueでも似たようなのがあって、どうやって型付けたらいいねんコレ!っていう。

[Question] How to get typesafe injection · Issue #256 · mobxjs/mobx-react · GitHub

結論としては、PropsではOptionalにしておいて、使うときに`!`を付ける。

interface EntryAppProps {
  store: EntryStore;
  event?: EntryEvent; // コレと
}

const EntryApp: React.SFC<EntryAppProps> = ({ store, event }) => {
  const { search, ui, tabNames } = store;
  const { onClickTab, onLoginSubmit, onChangeKeyword } = event!; // コレがポイント

  return (
    <div className="EntryApp">
      {/* ... */}
    </div>
  );
};

export default inject('event')(observer(EntryApp));

こうしないと、`event`もちゃんと渡せ!って怒られる。

おわりに

Flowを捨てた身としては、JavaScriptに型を付けるにはTypeScript一択になってしまったので、少しでも触っておきたかったという話。

ただ規模がそれなりであろうと、個人でやってる分には手間暇かけてまで型を付けたいとは思わんなぁーという。