Reactの公式チュートリアルをTypeScriptでやってみる
最近、Reactを勉強し始めました。
ある程度概念がわかってきたので、公式チュートリアル(三目並べ)をやることにしました。
でも、普通にやるとおもしろくないので、TypeScriptでやってみました。
はじめに
はじめに述べておきますが、ReactとTypeScriptの両方があまりわからない状態でやると、結構ハマるかもしれません。
僕は、Reactについてはある程度調べていて(コードは全く書いていませんが)、TypeScriptを使うのは初めてという状態でやりましたが、結構苦労しました。
ですが、ハマったところをいろいろ調べて勉強になったので、できれば、まずは自力でやってみることをお勧めします。
開発環境のセットアップ
僕は、最初の環境を Create React App で作りました。
オプションを指定すると、TypeScript用でセットアップされます。
$ create-react-app my-app --scripts-version=react-scripts-ts
この後、チュートリアルの Starter Code をコピペします。
僕は、HTMLは public/index.html に、CSSは src/index.css に、その他のコードはtsxファイルに入れました。ちなみに、各クラスごとにファイルを分割しました。
では、この後は、TypeScriptでやった時に、変更が必要なポイントを述べていきます。
型宣言
Boardクラスのメソッド renderSquare(i) は、TypeScriptでは型宣言をしなければなりません。
まあ、TypeScriptですから、これくらいは当たり前ですね。
renderSquare(i: number)
プロパティはインターフェース定義が必要
Board側でSquareのvalueプロパティを指定したり、TypeScriptでは怒られます。
[ts] Property 'value' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<Square> & Readonly<{ children?: ReactNode; }> & Re...'.
Squareコンポーネントでインターフェースを定義する必要があります。
interface ISquareProps { value: number; } class Square extends React.Component<ISquareProps, {}>
この後で、別のプロパティの追加もありますが同様です。
なお、インターフェース名を ISquareProps としていますが、Create React App のデフォルトだと、TSLintではじかれます。
tslint.json の
"interface-name": [true, "never-prefix"],
の箇所を
"interface-name": [true, "always-prefix"],
か false
にする必要があります。
どうしてこういう設定になっているのか不思議ですが。
ステートもインターフェース定義が必要
Squareのコンストラクターでステートを初期化していますが、ここでもインターフェース定義が必要になります。
チュートリアルでは、valueをnullで初期化していますが、stringで定義するとできないようなので、空文字で初期化します。
interface IState { value: string; } class Square extends React.Component<ISquareProps, IState> { constructor() { super(); this.state = { value: '', }; }
この後でstateをBoardクラスに移動しますが、同様なので省略します。
onClickプロパティの追加
これも上記のISquarePropsインターフェースに追加すればよいのですが、ファンクションの場合はどう定義すればいいのか悩みました。
よくわからなかったので、試しに onClick: any;
で定義してみましたが、以下のようなワーニングが出ました:
Type declaration of 'any' loses type-safety. Consider replacing it with a more precise type, the empty type ('{}'), or suppress this occurrence.
しばらく悩んだ挙句、ファンクションのインターフェースを定義するとうまくいきました。(これが正しい方法なんでしょうか?)
interface INoParamHandler { (): void; } interface ISquareProps { value: string; onClick: INoParamHandler; }
Functional Components
SquareをReact.Componentから継承するのではなく、ファンクションに変更するということですが、これはどうなんだろう。
実際の実装ではあまりやらなそうなので、僕はスキップしました。
まとめ
振り返ってみると、基本的には型宣言とインターフェース定義だけを気をつければ大きな問題はないですね。
ただ、最初はstate関連の定義のやり方がわからないと思うので、そこがハマりどころでしょうか。
当初は、React+Reduxのチュートリアルをやろうかと思っていましたが、Reactのみのチュートリアルでも一般的な説明にない概念がいくつか出てきたので、このチュートリアルをやってよかったです。