フロント開発といえば、React

Vue.jsと人気を二分していますね。 そんな私は、Angular使いがち、全部入りの方が安心するんです。

今更ですが、React始めてみます。

でも言語はTypeScript。鳥頭なのでjavascriptでちょっと大きなコードを書くと、あっという間に破綻するのです。

Adding TypeScriptを参考に勧めます。

動かしてみる

環境確認を兼ねて、ジェネレーターでてきたアプリをそのまま動かしてみます。 環境は

  • Windows 10
  • node.js v10.13.0

です。

ジェネレーターをアンインストール

公式に

If you’ve previously installed create-react-app globally via npm install -g create-react-app, we recommend you uninstall the package using npm uninstall -g create-react-app to ensure that npx always uses the latest version.

とあります。インストールじゃなくアンインストールしろという事らしいです。

> npm uninstall -g create-react-app

こうすると、勝手に最新版を使うようになるそうです。黒魔術。

プロジェクト作成

–typescriptオプションを付けてジェネレーターを実行します。

1
2
> npx create-react-app my-react-app --typescript
> cd my-react-app

そうすると、tsやらtsxやらで生成されます。

manifest.jsonなんてのも見えますね。PWA対応してるのかな???

実行

> npm start

で実行されて、ブラウザも勝手に開きます。

Node.js環境があれば、動かすだけなら簡単。

チュートリアルをやってみる

完全初心者なので、チュートリアルを見ながら、三目並べを作ってみます。

取りあえず動かす

見た目だけでもチュートリアルの初期状態に持っていきます。

index.css

チュートリアルのcssをまるっとコピーします。

App.tsx

App.tsxにJSのクラス部分を追加。

‘renderSquare(i) {‘で、型が無いと怒られるので、number型にする。

Appを

1
2
3
const App: React.FC = () => {
    return <Game />;
};

に変更。ちゃんと補完されるところがうれしい。

npm start で一応動く。

「データを Props 経由で渡す」をtypescriptでやる

データを Props 経由で渡すというのがありますが、typescriptだと

TypeScript error in C:/usr/src/my-react-app/src/App.tsx(19,17): Type ‘{ value: number; }’ is not assignable to type ‘IntrinsicAttributes & IntrinsicClassAttributes & Readonly<{}> & Readonly<{ children?: ReactNode; }>‘. Property ‘value’ does not exist on type ‘IntrinsicAttributes & IntrinsicClassAttributes & Readonly<{}> & Readonly<{ children?: ReactNode; }>‘. TS2322

などと怒られます。 Propsの型を明示的に指定してあげると、補完機能とともにデータが渡せるようになるようです。具体的には Square クラスに型引数を追記します。

1
class Square extends React.Component<{ value: number }> {

「インタラクティブなコンポーネントを作る」をtypescriptでやる

ここのミソはstateですね。

これもPropsと同じく、コンポーネントの型引数で型を指定するようです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Square extends React.Component<{ value: number }, { value: string }> {
    //propsの型とstateの型を指定
    constructor(props: { value: number }) {
        super(props);
        this.state = { value: '' };
    }
    render() {
        return (
            <button className="square" onClick={() => this.setState({ value: 'X' })}>
                {this.state.value}
            </button>
        );
    }
}

こんな感じでしょうか。

State のリフトアップ

ここでのポイントは

  • ゲームの状態を親コンポーネント(Board)で管理する
  • 子コンポーネント(Square)から親コンポーネント(Board)にイベントを通知する

でしょうか。

typesecipt化するには、BoardにPropsの型定義と、Squareにイベントハンドラの型定義を追加する必要があるようです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Square extends React.Component<{ value: string; onclick: () => void }> {

....

class Board extends React.Component<{}, { squares: string[] }> {
    constructor(props: {}) {
        super(props);
        this.state = {
            squares: Array<string>(9).fill('')
        };
    }

....

こんな感じ。Squareのstateは使わなくなるので削除してます。

State のリフトアップ、再び

型引数が複雑になってきたので、interfaceを定義してみます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
interface BoardProp {
    squares: string[];
    onClick?: (arg0: number) => void;
}
class Board extends React.Component<BoardProp> {

....

interface GameState {
    history: BoardProp[];
    xIsNext: boolean;
}
class Game extends React.Component<{}, GameState> {

リフトアップによりBoardにはステートを持たなくなるので、Boardの型引数はProp用のもののみになります。

過去の着手の表示

this.jumpTo(move)は未実装だとコンパイルが通らないので、

1
<button onClick={() => /*this.jumpTo(move)*/ null}>{desc}</button>

こんな感じでお茶を濁します。onclick無しでも良いかと。

出来上がりイメージ

童心に帰ろう。。。

コードはこちらです。

React手軽ですね。直観的だし、流行るのもわかります。

双方向バインディングとかどうやるのかなー。