HTML5テクニカルノート
Create React App 入門 02: クリックしたマス目にXをつける
- ID: FN2001001
- Technique: ECMAScript 2015
- Library: React 16.12.0
Create React Appのひな形からチュートリアルと同じマルバツゲームをつくる「Create React App 入門」シリーズ第2回は、クリックしたマス目に「X」印をつけます。9つのマス目のデータやその操作をどこで行い、コンポーネント間でどのようにやり取りするのかがポイントです。
01 データをルートコンポーネントにもたせる
マルバツのデータは9マス分まとまっていないと、勝ち負けの判定などの処理ができません。そこで、ルートコンポーネントsrc/components/App.js
にもたせることにしましょう。盤面のコンポーネント(Board
)にまとめることもできないではありません。けれど、盤面はデータの表示、データとその操作はルートコンポーネントとするのが、わかりやすい切り分けです。
データをもって子コンポーネントに渡し、参照させるためには、src/components/App.js
は関数でなくクラスコンポーネントに書き直さなければなりません。クラスコンポーネントは、つぎのようにReact.Component
を継承します。必ず備えなければならないのが、render()
メソッドです。関数コンポーネントに定めていたreturn
文を、この本体に移します。そして、インスタンスにデータを収める特別なプロパティがstate
です。constructor()
の中で、オブジェクトのかたちでプロパティにデータを与えて初期化してください(「constructor()」参照)。9マスのデータ(squares
)は配列にして、要素は確認用にすべて文字列'X'にしました。
src/components/App.js親コンポーネントから属性(// function App() { class App extends React.Component { constructor(props) { super(props); this.state = { squares: Array(9).fill('X') }; } render() { return ( <div className="game"> {/* <Board /> */} <Board squares={this.state.squares} /> </div> ); } }
squares
)のかたちで与えられた値は、関数コンポーネントの引数(props
)から参照できます。その値を、さらにバケツリレーで子コンポーネント(Square
)に属性として渡しているのがつぎのsrc/components/Board.js
のコードです。これでルートコンポーネントのデータの値が、9つのマス目にそれぞれ示されます(図001)。
src/components/Board.jsconst Board = (props) => { const renderSquare = (i) => // <Square value={i} />; <Square value={props.squares[i]} /> };
図001■9つのマス目にデータの文字が示された
02 クリックしたマス目に印をつける
マス目ははじめ空白にし、クリックしたら印をつけるようにしましょう。このときも、データを書き替えるのはルートコンポーネントです。けれど、クリックするのはマス目ですから、今度は子から親への逆バケツリレーになります。テンプレートの要素がクリックされたことを捉えるのは、onClick
イベントです(「イベント処理」参照)。子コンポーネント(src/components/Square.js
)がprops
から参照して呼び出すハンドラ関数(onClick()
)は、親コンポーネント(src/components/Board.js
)が与えます。
src/components/Square.jssrc/components/Board.jsconst Square = (props) => { return ( // <button className="square"> <button className="square" onClick={props.onClick}> {props.value} </button> ); };
const Board = (props) => { const renderSquare = (i) => <Square onClick={() => props.onClick(i)} /> };
逆バケツリレーの最後尾でイベント(onClick
)を受け取ったルートコンポーネント(src/components/App.js
)は、ハンドラとして自らのメソッド(handleClick()
)を呼び出します(「コンポーネントに(onClick のような)イベントハンドラを渡すには?」参照)。引数(i
)に受け取るのがマス目のインデックスです。プロパティstate
は、constructor()
呼び出しのあとは直に書き替えることはできず、メソッドsetState()
を用いなければなりません。引数はオブジェクトで、変更するプロバティ(squares
)に新たな値を与えます。値となる配列は、スプレッド構文...
により複製したうえで要素を書き替えました。
src/components/App.jsclass App extends React.Component { constructor(props) { this.state = { squares: Array(9).fill(null) // 'X') }; this.handleClick = this.handleClick.bind(this); } handleClick(i) { const squares = [...this.state.squares]; squares[i] = 'X'; this.setState({squares: squares}); }; render() { return ( <div className="game"> <Board onClick={(i) => this.handleClick(i)} /> </div> ); } }
注意すべき点として、イベントハンドラのメソッド(handleClick()
)がthis
を参照する場合は、あらかじめconstructor()
の本体でFunction.prototype.bind()
によりthis
をバインドしておくようにしましよう(「関数をコンポーネントインスタンスにバインドするには?」参照)。これではじめはnull
で空白にしたマス目に、クリックするとX印がつきます(図002)。
図002■クリックしたマス目にX印がつく
データの変更にはイミュータビリティが大切
前掲コードでは、変更するプロバティ(squares
)に、値の配列はスプレッド構文...
で複製してsetState()
メソッドにより設定しました。もとのデータを直に書き替えない、これがイミュータビリティ(immutability)です(「イミュータビリティは何故重要なのか」参照)。
オブジェクトの中身に直接手を加えると、Reactがいつどのような変更があったのか、検出に手間取ります。新たなデータで上書きすれば、変更がはっきりし、その影響範囲とくに描画の更新も最適化されるのです。
また、イミュータビリティは、機能の実装をシンプルにしたり、バグが見つけやすくなるという利点もあります。
書き替えた3つのモジュールは、つぎのコード001にまとめます。また、CodeSandboxに以下のサンプル001を掲げました。
コード001■クリックしたマス目にXをつける
src/components/App.js
import React from 'react';
import Board from './Board';
import './App.css';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null)
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(i) {
const squares = [...this.state.squares];
squares[i] = 'X';
this.setState({squares: squares});
};
render() {
return (
<div className="game">
<Board
squares={this.state.squares}
onClick={(i) => this.handleClick(i)}
/>
</div>
);
}
}
export default App;
import React from 'react';
import Square from './Square';
const Board = (props) => {
const renderSquare = (i) =>
<Square
value={props.squares[i]}
onClick={() => props.onClick(i)}
/>
return (
<div>
<div className="board-row">
{renderSquare(0)}
{renderSquare(1)}
{renderSquare(2)}
</div>
<div className="board-row">
{renderSquare(3)}
{renderSquare(4)}
{renderSquare(5)}
</div>
<div className="board-row">
{renderSquare(6)}
{renderSquare(7)}
{renderSquare(8)}
</div>
</div>
);
};
export default Board;
import React from 'react';
const Square = (props) => {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
};
export default Square;
サンプル001■Create React App: Tic Tac Toe 02
Create React App 入門
- Create React App 入門 01: 3×3のマス目をつくる
- Create React App 入門 02: クリックしたマス目にXをつける
- Create React App 入門 03: マルバツで勝ち負けを決める
- Create React App 入門 04: ゲームの履歴をさかのぼる
- Create React App 入門 05: 無駄な処理を省く
作成者: 野中文雄
作成日: 2020年01月03日
Copyright © 2001-2020 Fumio Nonaka. All rights reserved.