Reactを学習することになったのでチュートリアルをやってみようと思う。しかし、チュートリアルはTypescriptではないので、調べながら変更していこうと思う。
前回からのつづき
タイムトラベル機能
State のリフトアップ、再び
- トップレベルのAppコンポーネントで履歴を管理できるようにする。
// ↓ 追加
import { StateType } from "./Square"; // 追加
type HistoryType = {
squares: StateType[]
}
// ↑ 追加
const App = () => {
// ↓ Board.tsxから持ってくる
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}]);
// ↑ Board.tsxから持ってくる
- Boardコンポーネントが
squares
とonClick
をAppコンポーネントから受け取れるようにする。
// ↓ 追加
type BoardProps = {
squares: StateType[];
onClick: VoidFunction;
};
// ↑ 追加
// ↓ props: BoardProps を追加
const Board = (props: BoardProps) => {
// ↓ 削除
// const [xIsNext, setXIsNext] = React.useState<boolean>(true);
// const [squares, setSquares] = React.useState<StateType[]>(
// [null, null, null,
// null, null, null,
// null, null, null,]
// );
// ↑ 削除
const renderSquare = (i: number) => {
return <Square
value={props.squares[i]} // 変更
onClick={props.onClick} // 変更
/>;
};
- Appコンポーネントでゲームのステータステキストの決定や表示の際に最新の履歴が使われるようにする。
import React from 'react'; // 追加
import Board from './Board';
import { StateType } from "./Square";
type HistoryType = {
squares: StateType[]
}
// ↓ Board.tsxから移動
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;
}
// ↑ Board.tsxから移動
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 current = history[history.length - 1];
const winner = calculateWinner(current.squares);
// ↑ 追加
// ↓ Board.tsxから移動
let status: string;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (xIsNext ? 'X' : 'O');
}
const handleClick = (i: number) => {
const sq = squares.slice();
if (calculateWinner(sq) || sq[i]) {
return;
}
sq[i] = xIsNext ? 'X' : 'O';
setSquares(sq);
setXIsNext(prev => !prev);
};
// ↑ Board.tsxから移動
return (
<div className='game'>
<div className='game-board'>
<Board
squares={current.squares/* 変更 */}
onClick={handleClick/* 変更 */}
/>
</div>
<div className='game-info'>
<div>{status}</div> {/* 変更 */}
<ol>{/* TODO */}</ol>
</div>
</div>
);
};
export default App;
- Appコンポーネントに移動した処理をBoardコンポーネントから削除する。
import React from "react";
import Square, {StateType} from "./Square";
// ↓ 削除
// 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;
// }
// ↑ 削除
type BoardProps = {
squares: StateType[];
onClick: (i: number) => void; // 変更
};
const Board = (props: BoardProps) => {
// ↓ 削除
// const handleClick = (i: number) => {
// const sq = squares.slice();
// if (calculateWinner(sq) || sq[i]) {
// return;
// }
// sq[i] = xIsNext ? 'X' : 'O';
// setSquares(sq);
// setXIsNext(prev => !prev);
// };
// ↑ 削除
const renderSquare = (i: number) => {
return <Square
value={props.squares[i]}
onClick={props.onClick}
/>;
};
// ↓ 削除
// const winner = calculateWinner(props.squares);
// let status;
// if (winner) {
// status = 'Winner: ' + winner;
// } else {
// status = 'Next player: ' + (xIsNext ? 'X' : 'O');
// }
// ↑ 削除
return (
<div>
{/* <div className="status">{status}</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;
- Squareコンポーネントの
onClick
にpropsのonClickを渡すところでエラーになってしまうので、Squareコンポーネントを変更する。
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;
- 修正したSquareコンポーネントに合わせて、Board.tsxを修正する。
const renderSquare = (i: number) => {
return <Square
target={i} // 追加
value={props.squares[i]}
onClick={props.onClick}
/>;
};
- Appコンポーネントの
handleClick
メソッドに、新しい履歴をhistory
に追加する。
const handleClick = (i: number) => {
const current = history[history.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}])); // 追加
};
コメント