■1. 初期コード
class Square extends React.Component {//(1)
render() {//(2)
return (//(3)
<button className="square">//(4)
{/* TODO */}
</button>
);
}
}
◯1.2 Boardクラス
以下のコードは
Reactコンポーネントを継承したBoardクラスのことで、
renderSquare(i)はメソッドであり、iは描画するSquareの位置を示している。
描画する際に呼び出されるrenderメソッド内では文字列”Next player: X”が定義され、
全体を囲む<div>の中に現在のステータスを示す<div>とマス目を形成するための<div>が存在する。this.renderSquare(1)などの部分では、1.1で定義したSquareクラスが呼ばれ、ボタンの表示が更新されるようになっている。
class Board extends React.Component {
renderSquare(i) {
return <Square />;
}
render() {
const status = 'Next player: X';
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}
◯Gameクラス
以下のコードは
リアクトコンポーネントを継承したGameオブジェクトの定義であり、
game配下にgame-boardとgame-infoがあり、game-board内部には1.2のBoardクラス、game-info内部にはstatusとtodoが書いてあります。
game-info内部にはstatusとtodoが書いてあります。
class Game extends React.Component {
render() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<div>{/* status */}</div>
<ol>{/* TODO */}</ol>
</div>
</div>
);
}
}
■データをpropsで渡す
ここでは、Boardクラスの値をSquareクラスに渡す方法を記載しています。
BoardクラスのrenderSquareを呼び出しているところには、それぞれ0~8の値の引数を代入していたと思います。
まずはその引数iを関数の戻り値としてSquareクラスの値として返します。
class Board extends React.Component {
renderSquare(i) {
return <Square value={i} />;
}
Squareクラスでデータをもらうには、this.props.valueを呼び出します。
Reactでは親(Boardクラス)から子(Square)にデータを渡すにはpropsを使用します。
今回は値を渡しましたが、propsでは文字列やスタイル、イベントなどを渡すことができます。
class Square extends React.Component {
render() {
return (
<button className="square">
</button>
);
}
}
⬇︎現時点での状態は以下のようになっています。
■インタラクティブなコンポーネントを作成する。
0~8の各マスがクリックされたときに処理できるように以下のように修正します。
buttonコンポーネントのonClick={}では、ボタンコンポーネントがクリックされたときに行う処理を追加することができます。
現時点ではボタンが押されたことを確認したいため、コンソールにログを表示させるconsole.log()を記述してあります。
class Square extends React.Component {
render() {
return (
<button className="square" onClick={function() {console.log('click');}}>
</button>
);
}
}
今回は◯✖️ゲームをしたいため、クリックされた/クリックされていないという状態を保持しておく必要があります。
状態を保持するにはstateを使います。
具体的には以下のコードのように、コンストラクタ(クラスからオブジェクトを作成したときに、自動で実行されるもの)でthis.stateを定義することで状態を持てるようになります。
class Square extends React.Component {
constructor(props)
{
super(props)
{
this.state = { value: null, };
}
}
render() {
return (
<button className="square" onClick={function() {console.log('click');}}>
</button>
);
}
}
・なぜsuperを定義するのか?
Javascriptではサブクラスのコンストラクタ定義に必要であり、superを定義しないとthis.stateを使用できないからです。superは親のコンストラクタを参照するという意味があり、親のコンストラクタを呼ぶまでthisを使うことができないとされています。そのため、GameのサブクラスであるSquareクラスのコンストラクタにはsuper定義が必要となります。
・なぜsuperの引数をpropsとするのか?
コンストラクタが定義されるまでthis.propsが設定されないため、引数でpropsを定義しておく必要があります。
■9つのSquareクラスのstateを1箇所で管理する。
Boardクラスを以下のように書き換える。
class Board extends React.Component {
constructor(props)
{
super(props);
this.state = {
squares: Array(9).fill(null),
};
}
handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = 'X';
this.setState({squares: squares});
}
renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}
/>
);
}
render() {
const status = 'Next player: X';
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}
Squareクラスを書き換える
class Square extends React.Component {
render() {
return (
<button
className="square"
onClick={() => this.props.onClick()}
>
</button>
);
}
}
①Boardクラスのコンストラクタ
◯×ゲームをするとき、初期状態は何もない状態ですよね。従って、初期状態=コンストラクタでnullを指定します。squaresの配列をnullで埋めています。
②renderSquareでは、Squareがレンダーされたときに状態=stateをBoardが取得できるようにします。
③またrenderSquareではクリックされたときにhandleClickを呼ぶようにします。handleClickではクリックされたSquare配列に'X'という文字を入れて、stateにSetします。
■Squareクラスを関数コンポーネントに置き換える
今までクラスを使用していましたが、stateを持たない関数コンポーネントに置き換えます。
引数をpropsにすることで、表示すべき内容を返す関数とします。
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
</button>
);
}
■XやOを表示させるために、先手をXとなるよう定義する
Boardのコンストラクタとして、初期はnullを詰めていますが、「xIsNext」により、毎回反転
されます。
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
xIsNext: true,
};
}
またhandleClickを書き換えることで、毎回交互にXとOを表示するように変えます。
handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
});
}
■どちらの手番かどうかを表示する
Boardクラスのrenderにも同様に適用することで、次の手番がどちらなのかを表示する。
render() {
const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
■ゲーム勝者の判定
ファイル末尾に以下をコピペする。この関数の横並びになっている[0,1,2]や[1,4,7]は9つのマス目の縦横斜めの一直線を表しています。8通りありますが、これのどれかが同じ文字(XまたはO)で満たされたときに満たした文字を返すようにしています。
function calculateWinner(squares) {
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のrendor付近に以下のコードを追加します。これによりレンダリングされるたびに勝利しているかどうかを判定します。もし勝利していれば、勝者を表示します。勝利していなければ次の手番を表示します。
render() {
const winner = calculateWinner(this.state.squares);
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
}
以上がReactチュートリアルとなります。
最後にコードを全て載せておきます。
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
</button>
);
}
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
xIsNext: true,
};
}
handleClick(i) {
const squares = this.state.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
});
}
renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}
/>
);
}
render() {
const winner = calculateWinner(this.state.squares);
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
}
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}
class Game extends React.Component {
render() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<div>{/* status */}</div>
<ol>{/* TODO */}</ol>
</div>
</div>
);
}
}
// ========================================
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Game />);
function calculateWinner(squares) {
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;
}
■React入門にはこちらがオススメ:
これからはじめるReact実践入門 コンポーネントの基本からNext.jsによるアプリ開発まで