Reactを学習することになったのでチュートリアルをやってみようと思う。しかし、チュートリアルはTypescriptではないので、調べながら変更していこうと思う。
今回で一応完成します。
前回からのつづき
タイムトラベル機能
過去の着手の表示
history
にmap
メソッドを作用させ、過去の手番にジャンプするためのボタンを生成する。
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>
);
タイムトラベルの実装
- 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>
);
});
- stateに
stepNumber
を追加して、何手目の状態かを管理する。
const [history, setHistory] = React.useState<HistoryType[]>([{squares: squares}]);
const [stepNumber, setStepNumber] = React.useState<number>(0); // 追加
jumpTo
メソッドで、stepNumber
を更新する。また、更新しようとしているstepNumber
が偶数の場合、xIsNext
をtrueにする。
const jumpTo = (move: number) => {
setStepNumber(move);
setXIsNext((move % 2) === 0);
};
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) // 追加
};
stepNumber
によって、現在選択されている着手をレンダーするように修正する。
const current = history[stepNumber]; // 変更
const winner = calculateWinner(current.squares);
完成!
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.
コメント