LoginSignup
3
2

More than 3 years have passed since last update.

React のチュートリアルを Typescript でやってみた2(ゲーム完成まで)

Last updated at Posted at 2020-07-11

目次

概要

チュートリアル通りにやりますが、端折る箇所もあると思います。

各々保管をお願いいたします。

公式のチュートリアルページ

今回の実施チュートリアル内容

ゲームを完成させる

  1. Stateのリストアップ
  2. イミュータビリティは何故重要なのか
  3. 関数コンポーネント
  4. 手番の処理
  5. ゲーム勝者の判定

1.Stateのリストアップ

Squareに値を当てられるようにする

チュートリアルページの様にBoardでSquareの値を管理するように書いてみます。

まずは、square.tsxの修正
Propsでデータを受け取れるようPropsを定義します。
このとき使用しなくなったStateは削除しておきます。

また、valueの方はString型を定義します。
今回扱う文字がXOのためになりますが、チュートリアルでは他にもnullを扱っています。
Typescriptの型の都合上今回は空文字にします。
しっかりとチュートリアルどおりにやりたい場合は、Stringとnullを同時に扱える定数を提示しそちらを使うようにしましょう。

square.tsx
+ type Props = {
+   value: String
+ }

- type State = {
-   value: String
- }

Stringとnullを定義したもの

type SquareVaule = String | null;

type Props = {
  value: SquareVaule;
}

次に、board.tsxの改修を進めていきます。
boardのコンポーネントでsquareのコンポーネントのデータを扱うためStateを定義します。

board.tsx
type State = {
  squares: Array<String>;
}

次にコンポーネントの引数の部分について修正
以下のようにしました。
{}の部分は、Propsに当たります。今回は使用していないため空のものを設定
わかりにくい場合は、Propsの空の定義を作成し入れてあげるといいでしょう。
どちらがいいのかわからないです。その時時によると思います。

board.tsx
class Board extends React.Component<{}, State> {

以下は丁寧にした感じのソースです。

type Props = {}

class Board extends React.Component<Props, State> {

次にコンストラクタを定義します。
以下の用な感じにこちらはほぼチュートリアルのソース通りです。

board.tsx
  constructor(props: {}) {
    super(props)
    this.state = {
      squares: Array<String>(9).fill(""),
    }
  }

あとは、Squareのコンポーネントにstateのデータを指定するだけです。

board.tsx
  renderSquare(i: number) {
    return <Square value={this.state.squares[i]} />;
  }

以上で、Squareに文字列を設定することができるようになります。

マス目がクリックされたときの動作を作成する

マス目がクリックされたときの動作を作成します。
まずは、square.tsxのPropsを修正します。
boardから渡ってくる動作を受け取れるように以下の様にonClickを設定します。

square.tsx
type Props = {
  value: String
+  onClick: () => void;
}

Boardで追加したonClickの部分について作成していきます。
まずは、board.tsxでSquareにonClickを渡す部分を追加します。

board.tsx
  renderSquare(i: number) {
    return <Square
      value={this.state.squares[i]}
      onClick={() => this.handleClick(i)}
    />;
  }

次にhandleClickについて作成します。

board.tsx
  handleClick(i: number) {
    const squares = this.state.squares.slice();
    squares[i] = 'X';
    this.setState({squares: squares});
  }

最後に渡したonClickの動作を動作するようにします。

square.tsx
  render() {
    return (
      <button
        className="square"
        onClick={() => this.props.onClick()}
      >
        {this.props.value}
      </button>
    );
  }

2.イミュータビリティは何故重要なのか

特にソースを変更する必要なし。

3.関数コンポーネント

Squareクラスを関数コンポーネントに書き換えます。
引数の部分に型を入れるだけになった。

square.tsx
function Square(props: Props) {
  return (
    <button className="square" onClick={props.onClick}>
      {props.value}
    </button>
  );
}

4.手番の処理

とりあえず、Stateの中にユーザ判定用のフラグを用意しました。

board.tsx
type State = {
  squares: Array<String>;
+  xIsNext: boolean;
}

コンストラクタ内で初期値の設定を追加

board.tsx
  constructor(props: {}) {
    super(props)
    this.state = {
      squares: Array<String>(9).fill(""),
+      xIsNext: true,
    }
  }

マス目がクリックされた際の動作を追加します。
追加した動作
1. ○と×の入力切替
2. プレイヤーのフラグの変更

board.tsx
  handleClick(i: number) {
    const squares = this.state.squares.slice();
+    squares[i] = this.state.xIsNext ? 'X' : '';
    this.setState({
      squares: squares,
+      xIsNext: !this.state.xIsNext,
    });
  }

表示プレイヤーの切り替え

board.tsx
  const status = 'Next player: ' + (this.state.xIsNext ? 'X' : '');

5.ゲーム勝者の判定

勝敗判定のfunctionを追加します。
引数はStringの配列に設定

board.tsx
  calculateWinner(squares: Array<String>) {
    const lines = [
      [0, 1, 2],
      [3, 4, 5],
      [6, 7, 8],
      [0, 3, 6],
      [1, 4, 7],
      [2, 5, 8],
      [0, 4, 8],
      [2, 4, 6],
    ];
    for (let i = 0; i < lines.length; i++) {
      const [a, b, c] = lines[i];
      if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
        return squares[a];
      }
    }
    return null;
  }

勝敗のfunctionを使用しプレイヤーが勝利した場合プレイヤーの表示方法を変更する。

board.tsx
  render() {
-    const status = 'Next player: ' + (this.state.xIsNext ? 'X' : '');

+    const winner = this.calculateWinner(this.state.squares);
+    let status;
+    if (winner) {
+      status = 'Winner: ' + winner;
+    } else {
+      status = 'Next player: ' + (this.state.xIsNext ? 'X' : '');
+    }

勝敗がついた場合やマス目がすべて入力された場合に何も動作しないようにする。

board.tsx
  handleClick(i: number) {
    const squares = this.state.squares.slice();
+    if (this.calculateWinner(squares) || squares[i]) {
+      return;
+    }
    squares[i] = this.state.xIsNext ? 'X' : '';
    this.setState({
      squares: squares,
      xIsNext: !this.state.xIsNext,
    });
  }

まとめ

とりあえずゲーム完成までやってみました。
公式通りにやったためそこまで詰まるところはなかった。
型をちゃんと意識すれば良さそうですね。

ここまでのソースは以下になります
- ゲーム完成までのブランチ

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2