ReactのチュートリアルをTypescriptでやってみる-5

React

Reactを学習することになったのでチュートリアルをやってみようと思う。しかし、チュートリアルはTypescriptではないので、調べながら変更していこうと思う。

今回で一応完成します。

前回からのつづき

タイムトラベル機能

過去の着手の表示

  1. historymapメソッドを作用させ、過去の手番にジャンプするためのボタンを生成する。
  const current = history[history.length - 1];
  const winner = calculateWinner(current.squares);

  // ↓ 追加
  const jumpTo = (move: number) => {

  };

  const moves = history.map((step, move) => {
    const desc = move ?
      'Go to move #' + move :
      'Go to game start';
    return (
      <li>
        <button onClick={() => jumpTo(move)}>{desc}</button>
      </li>
    );
  });
  // ↑ 追加


・・・


  return (
    <div className='game'>
      <div className='game-board'>
        <Board
          squares={current.squares} 
          onClick={handleClick} 
        />
      </div>
      <div className='game-info'>
        <div>{status}</div>
        <ol>{moves}</ol> {/* 変更 */}
      </div>
    </div>
  );
ボタンをクリックしても何も起きない

タイムトラベルの実装

  1. li 要素にkeyを設定する。
    keyを設定しないと警告が表示される。
    keyはコンポーネントとその兄弟の間で一意にする必要がある。
  const moves = history.map((step, move) => {
    const desc = move ?
      'Go to move #' + move :
      'Go to game start';
    return (
      <li key={move}> {/* 変更 */}
        <button onClick={() => jumpTo(move)}>{desc}</button>
      </li>
    );
  });
  1. stateにstepNumberを追加して、何手目の状態かを管理する。
  const [history, setHistory] = React.useState<HistoryType[]>([{squares: squares}]);
  const [stepNumber, setStepNumber] = React.useState<number>(0); // 追加
  1. jumpToメソッドで、stepNumberを更新する。また、更新しようとしているstepNumberが偶数の場合、xIsNextをtrueにする。
  const jumpTo = (move: number) => {
    setStepNumber(move);
    setXIsNext((move % 2) === 0);
  };
  1. handleClickメソッドで、stepNumberを更新する。また、stateのhistoryを直接読むのではなく、stepNumberを利用する。
  const handleClick = (i: number) => {
    const hist = history.slice(0, stepNumber + 1); // 追加
    const current = hist[hist.length - 1]; // 変更
    const sq = current.squares.slice();
    if (calculateWinner(sq) || sq[i]) {
      return;
    }
    sq[i] = xIsNext ? 'X' : 'O';
    setSquares(sq);
    setXIsNext(prev => !prev);
    setHistory(prev => prev.concat([{squares: sq}]));
    setStepNumber(hist.length) // 追加
  };
  1. stepNumberによって、現在選択されている着手をレンダーするように修正する。
  const current = history[stepNumber]; // 変更
  const winner = calculateWinner(current.squares);

完成!

startから順番にクリック
import React from 'react';
import Board from './Board';
import { StateType } from "./Square";

type HistoryType = {
  squares: StateType[]
}
const calculateWinner = (squares: StateType[]) => {
  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;
}

const App = () => {
  const [xIsNext, setXIsNext] = React.useState<boolean>(true);
  const [squares, setSquares] = React.useState<StateType[]>(
    [null, null, null,
    null, null, null,
    null, null, null,]
  );
  const [history, setHistory] = React.useState<HistoryType[]>([{squares: squares}]);
  const [stepNumber, setStepNumber] = React.useState<number>(0);

  const current = history[stepNumber];
  const winner = calculateWinner(current.squares);

  const jumpTo = (move: number) => {
    setStepNumber(move);
    setXIsNext((move % 2) === 0);
  };

  const moves = history.map((step, move) => {
    const desc = move ?
      'Go to move #' + move :
      'Go to game start';
    return (
      <li key={move}>
        <button onClick={() => jumpTo(move)}>{desc}</button>
      </li>
    );
  });

  let status: string;
  if (winner) {
    status = 'Winner: ' + winner;
  } else {
    status = 'Next player: ' + (xIsNext ? 'X' : 'O');    
  }
  const handleClick = (i: number) => {
    const hist = history.slice(0, stepNumber + 1);
    const current = hist[hist.length - 1];
    const sq = current.squares.slice();
    if (calculateWinner(sq) || sq[i]) {
      return;
    }
    sq[i] = xIsNext ? 'X' : 'O';
    setSquares(sq);
    setXIsNext(prev => !prev);
    setHistory(prev => prev.concat([{squares: sq}]));
    setStepNumber(hist.length);
  };

  return (
    <div className='game'>
      <div className='game-board'>
        <Board
          squares={current.squares} 
          onClick={handleClick} 
        />
      </div>
      <div className='game-info'>
        <div>{status}</div>
        <ol>{moves}</ol>
      </div>
    </div>
  );
};
export default App;
import React from "react";
import Square, {StateType} from "./Square";

type BoardProps = {
  squares: StateType[];
  onClick: (i: number) => void;
};

const Board = (props: BoardProps) => {

  const renderSquare = (i: number) => {
    return <Square
      target={i}
      value={props.squares[i]}
      onClick={props.onClick}
    />;
  };
  return (
    <div>
      <div className="borad-row">
        {renderSquare(0)}
        {renderSquare(1)}
        {renderSquare(2)}
      </div>
      <div className="borad-row">
        {renderSquare(3)}
        {renderSquare(4)}
        {renderSquare(5)}
      </div>
      <div className="borad-row">
        {renderSquare(6)}
        {renderSquare(7)}
        {renderSquare(8)}
      </div>
    </div>
  );
};
export default Board;
import React from 'react';

type SquareProps = {
    value: StateType;
    target: number;
    onClick: (i: number) => void;
  };
export type StateType = 'O' | 'X' | null;

const Square = (props: SquareProps) => {
  const handleClick = (event: React.MouseEvent<unknown>) => {
    props.onClick(props.target);
  };

  return (
      <button
        className="square"
        onClick={handleClick}
      >
        {props.value}
      </button>
    );
  };
  export default Square;
  

See the Pen
Untitled
by yoshix3 (@yoshix3-the-selector)
on CodePen.

コメント

タイトルとURLをコピーしました