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のみのチュートリアルでも一般的な説明にない概念がいくつか出てきたので、このチュートリアルをやってよかったです。