はじめに
React Tutorialの翻訳です。英語そこまでできるわけでないので直訳なのと間違いは多々あるかと思いますのでご了承ください。
この翻訳は以下の2016/10/29時点のものとなります。
https://facebook.github.io/react/tutorial/tutorial.html
翻訳ここから
チュートリアル:React入門
概要
我々が構築するもの
今日、我々は対話型な三目並べ(マルバツ)ゲームを構築します。我々はHTMLとJavaScriptに精通していることを前提としますが、あなたがたとえ以前に使ったことがなかったとしても後からついていくことができるべきです。
もしよければ、あなたは最終的な結果をここで確かめることができます: Final Result。ゲームを遊んでみてください。あなたはまたMoveリストとのリンクをクリックして「過去に戻る」ことができ、Moveが作られた直後と同じボードを見ることができます。
Reactとは何か?
Reactはユーザーインターフェースの構築のための、宣言型で、効率的で、柔軟なJavaScriptライブラリです。
Reactはいくつかのやや異なるコンポーネントを持ち、我々はReact.Componentのサブクラスで始めます:
class ShoppingList extends React.Component {
render() {
return (
<div className="shopping-list">
<h1>Shopping List for {this.props.name}</h1>
<ul>
<li>Instagram</li>
<li>WhatsApp</li>
<li>Oculus</li>
</ul>
</div>
);
}
}
// Example usage: <ShoppingList name="Mark" />
我々は直ちに面白いXMLライクなタグを取得します。あなたのコンポーネントはあなたが描写したいReactを読み出し、それからReactはあなたのデータが変更された時に効率的に更新し、ただしいコンポーネントを描写します。
ここの、ShoppingListはReactコンポーネントクラスまたは*Reactコンポーネントタイプ**です。コンポーネントはパラメータを取り、props
を呼び、そしてrender
メソッドを経由してディスプレイにViewの階層構造を返します。
render
メソッドはあなたが描写ものの_記述_を返し、それからReactはスクリーンに記述と描写のそれを与えます。特に、render
は描写するものの軽い記述であるReactエレメントを返します。多くのReact開発者はこれらの構造を簡単に書ける特別なJSXと呼ばれる構文を使います。<div />
構文はビルド時にReact.createElement('div')
へと変換されます。以下の例は同等のものです。
return React.createElement('div', {className: 'shopping-list'},
React.createElement('h1', ...),
React.createElement('ul', ...)
);
あなたはどんなJavaScriptの表現もJSXの中の中かっこの内部でおくことができます。それぞれのReactエレメントは、変数として保存される、またはあなたのプログラムで通過できる本物のJacaScriptオブジェクトです。
ShoppingList
コンポーネントはただ内蔵されたDOMコンポーネントを描画しますが、あなたは<ShoppingList />
と書くことによって、簡単にカスタムReactコンポーネントを構成できます。それぞれのコンポーネントはカプセル化されており、独立して操作でき、シンプルなコンポーネントの外側で複合されたUIを構築することをあなたに許可します。
入門
この例で始めましょう: Starter Code。
今日我々が構築するもののシェルを含んでいます。我々はJavaScriptについて心配する必要があるだけのスタイルを提供します。
特に、我々は三つのコンポーネントを持ちます:
- Square
- Board
- Game
Squareコンポーネントは一つの <div />
を描画し、Boardは九つのSquareを描画し、そしてGameコンポーネントは我々があとから埋めるいくつかの代替物と共にBoardを描画します。ここのポイントはいずれのコンポーネントも双方向ではないということです。
(JSファイルの最後では、我々が後から使うヘルパー関数calculateWinner
も定義します。)
Propsを通してデータを受け渡す
手始めにやってみるために、BoardコンポーネントからSquareコンポーネントにあるデータを渡してみましょう。BoardのrenderSquare
メソッドのなかで、<Square value={i} />
を返すようコードを変え、それからSquareのrenderメソッドを、{/* TODO */}
を{this.props.value}
に変えることによって値を表示するよう変えてください。
変更前:
https://facebook.github.io/react/img/tutorial/tictac-empty.png
変更後:
https://facebook.github.io/react/img/tutorial/tictac-numbers.png
対話型コンポーネント
クリックした時に「X」で埋めるSquareコンポーネントを作りましょう。Square
クラスのrender()
関数の中で返すタグを変えましょう:
<button className="square" onClick={() => alert('click')}>
これは新しいJavaScriptアロー関数の構文を使います。もしあなたが四角上をクリックすると、ブラウザ上でアラートが表示されるでしょう。
Reactコンポーネントはコンストラクタ内でthis.state
を設定することによって状態を保つことができ、これはコンポーネントをプライベートにするのによく考えるべきです。状態の中に四角の現在地を保存してみましょう。そして四角がクリックされた時にそれを変えてみましょう。最初に、状態を初期化するためにクラスへコンストラクタを追加します:
class Square extends React.Component {
constructor() {
super();
this.state = {
value: null,
};
}
...
}
JavaScriptのクラスのなかで、サブクラスのコンストラクタを定義する時に、あなたは明確にsuper()
を呼び出す必要があります。
今、this.props.value
の代わりにthis.state.value
を表示するrender
メソッドを変更し、アラートの代わりに() => this.setState({value: 'X'})
になるようイベントハンドラを変えます:
<button className="square" onClick={() => this.setState({value: 'X'})}>
{this.state.value}
</button>
this.setState
が呼ばれる時は必ず、コンポーネントの更新が予定され、渡された状態の更新と、コンポーネントの描画をそれらの後継と合わせてReactにマージさせます。コンポーネントを描画するとき、this.state.value
はX
になり、あなたはグリッドの中にXを見ます。
もしあなたがどの四角でもクリックすると、Xはその中に表示されるはずです。
開発者ツール
ChromeとFirefoxのためのReact Devtoolsエクステンションはあなたのブラウザのdevtoolの中でReactコンポーネントツリーの検査を可能にします。
ツリーの中のオンポーネントのどんなpropsやstateも調査を可能にします。
これは複数のフレームのためにCodePenでは動作しませんが、もしあなたがCodePenにログインし(スパムを防ぐための)メールを確認することで、あなたは新しいタブであなたのコードを開くために Change View > Debugへ行くことができ、それからdevtoolsは動作します。もしあなたがこれをすることを欲しないならいいですが、それが存在することを知るのはいいことです。
状態を持ち上げる
我々は今、三目ゲームのための基本的な構成要素を持ちます。しかしたった今、stateはそれぞれのSquareコンポーネントの中にカプセル化されています。完全に動作するゲームを作るために、我々は今、一人のプレイヤーがゲームに勝ったかどうかを確認するのを必要とし、四角のなかでOとXを交互に配置する必要があります。だれが勝ったかどうかを確認するために、Squareコンポーネントにわたって分割するのではなく、ひとつの場所の中に九つの四角の値を持つ必要があります。
あなたは考えるでしょう、BoardはそれぞれのSquareの現在のstateは何であるか聞くべきだと。それは専門的に言うとReactの中ですることは可能ではありますが、理解するのに難しいコードを作る傾向があり、より不安定で、リファクタするには難しくなるので、それは落胆します。
かわりに、ここのベストな解決方法はそれぞれのSquareのなかに代わってBoardコンポーネントの中にこのstateを保存し、そしてBoardコンポーネントは表示するのが何であるか、それぞれのSquareを呼び出すことができます。それぞれの四角を作る方法がそのindexをより早く表示するように。
あなたが複数の子供からデータを集めたい、またはお互いにコミュニケーションする2つの子コンポーネントを持ちたいとき、親コンポーネントのなかで生存するようにstateを上向きに動かします。親はそれからstateをpropsを経由して子たちに後退し渡し、子コンポーネントはいつも親やお互いと同期します。
このように上向きにstateを引っ張ることはReactコンポーネントをリファクタリングするときに普通であり、これを試すための機会をとりましょう。9つの四角と相当し、9つのnullの配列を含んでいるBoardのために初期stateを加えましょう。
class Board extends React.Component {
constructor() {
super();
this.state = {
squares: Array(9).fill(null),
};
}
}
Boardは多少以下に似ており、我々は遅れてそれを埋めます。
[
'O', null, 'X',
'X', 'X', 'O',
'O', null, null,
]
それぞれの四角に値を渡します:
renderSquare(i) {
return <Square value={this.state.squares[i]} />;
}
そしてSquareを再びthis.props.value
を使うよう変更します。今、我々は四角がクリックされたときに何が起こったかを変えることを必要とします。Boardコンポーネントは、埋められた四角を保存し、それは我々はBoardのstateを更新するSquareのための方法を必要とすることを意味します。コンポーネントのstateはプライベートかを熟考することから、我々はSquareから直接Boardのstateを更新できません。たいていのこのパターンは四角がクリックされたときに呼ばれるSquareへBoardから関数を渡します。renderSquare
を再び変えてください:
return <Square value={this.state.squares[i]} onClick={() => this.handleClick(i)} />;
今、我々は2つのpropsをBoardからSquareへ渡します: value
とonClick
。後者はSquareが呼ぶことができる関数です。そしてSquareが持つrender
を変えることでそれをしてください:
<button className="square" onClick={() => this.props.onClick()}>
これは四角がクリックされたときを意味し、親から渡されたonClick関数を呼びます。onClick
はここでは特別な意味を持ちませんが、handle
でそれらを実行し、on
で始まるハンドラーpropsの名前として人気です。四角をクリックしてください。我々はまだhandleClick
を定義していないため、あなたはエラーを受け取るでしょう。Boardクラスに追加してください:
handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = 'X';
this.setState({squares: squares});
}
我々は存在している配列を変化させる代わりにsquare
配列をコピーするために.slice()
を呼びます。なぜ普遍性が重要かを学ぶために先のセクションへ飛びましょう。
今アナタは再びそれを埋めるために四角をクリックできるはずですが、stateはそれぞれのSquareの代わりにBoardコンポーネントに保存されており、それは我々にゲームを構築し続けさせます。Boardのstateが変わるときは必ず注意してください、Squareコンポーネントは自動的に描画します。
Squareはもはや自身のstateを保ちません; 自身の値を親のBoard
から受取り、クリックされたときに親に通知いsます。我々はこのコントロールされたコンポーネントのようなコンポーネントを呼びます。
なぜ普遍性は重要なのか
前のコードの例の中で、変化させる前に、そして存在する配列を変化させることを防ぐために、square
配列をコピーするslice()
オペレータを使うことを提案しました。この意味は何か、そしてなぜこの重要なコンセプトを学ぶのかについて話しましょう。
データを変更するための一般的な2つの方法があります。最初の、過去最もありふれた方法は、変数の値を直接変更することによってデータを変化させてしまうことです。二番目の方法は、望ましい変更も含むオブジェクトの新しいコピーとデータを取り替えることです。
変更によるデータの変化
var player = {score: 1}
player.score = 2 // 同じオブジェクトが変化した {score: 2}
変更しないデータの変化
var player = {score: 1}
player = {...player, score: 2} // 新しいオブジェクトは変化していない {score: 2}
最後の結果は同じですが、直接変更しないことによって(または基礎となるデータを変えることは)、我々はコンポーネントとアプリケーション全体のパフォーマンスを向上させることを助け、さらなる効果を得ます。
変更を追跡する
変化するオブジェクトが変化したかどうかを決定することは、直接オブジェクトが変更されるため、複雑です。これはそれから以前のコピーと現在のオブジェクトを比較し、全体のオブジェクトツリーを横断し、それぞれの変数と値を比較することを要求します。このプロセスはますます複雑になるでしょう。
どのように不変なオブジェクトが変化したかを決定することはかなり簡単です。もし参照されているオブジェクトが以前より異なるならば、オブジェクトは変化しています。以上です。
Reactで再描画するときを決定する
Reactでの普遍性の大きな効果はあなたがシンプルで純粋なコンポーネントを構築するときにきます。変更がそれを作ったかどうかを簡単に決定できる不変なデータはまた、コンポーネントが再描画さえることを要求したときに決定することを助けます。
どのように純粋なコンポーネントを構築するかを学ぶには、shouldComponentUpdate()を見てみましょう。また、不変なデータを厳密に遵守させるImmutable.jsライブラリを見てみましょう。
関数的コンポーネント
我々のプロジェクトに戻りまして、あなたは今Square
からconstructor
を削除できます; 我々はもう必要ありません。事実、Reactは、ただrender
メソッドから成るSquareのようなコンポーネントの種類のためのステートレスな関数的コンポーネントと呼ばれるシンプルな構文をサポートします。React.Componentを継承したクラスを定義するのではなく、何を描画するべきかを返しpropsを取る簡単に関数を書きます。
function Square(props) {
return (
<button className="square" onClick={() => props.onClick()}>
{props.value}
</button>
);
}
あなたは二回現れるthis.props
をprops
に変える必要があります。あなたのアプリのなかの多くのコンポーネントは関数的コンポーネントに書き換えられます; これらのコンポーネントは書くために簡単である傾向があり、Reactは将来よりそれらを最適化するでしょう。
交替ですること
我々のゲームの明らかな欠陥は、ただXのみが遊べることです。これを直しましょう。
最初の動きのデフォルトが「X」によってなるようにしましょう。Boardコンストラクタのなかの開始時のstateを修正します。
class Board extends React.Component {
constructor() {
super();
this.state = {
...
xIsNext: true,
};
}
我々が動く各時間で我々はbooleanの値をひっくり返し、stateを保存することによってxIsNext
を切り替えます。xIsNext
の値をひっくり返すためにhandleClick
関数を更新します。
handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
});
}
今、XとOが交替します。次に、Boardのrender
の中の「status」テキストを変え、次が誰かを表示するようにします。
勝者を宣言する
ゲームに勝った時に示しましょう。九つの値のリストを取るcalculateWinner(squares)
ヘルパー関数はファイルの下部で提供されています。誰かが勝った時に誰がゲームに勝ち、ステータステキストに「Winner: [X/O]」と表示するかどうかを確認するために、Boardのredner
関数の中で呼ぶことができます:
render() {
const winner = calculateWinner(this.state.squares);
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
}
...
}
あなたは今、もし誰かが既にゲームに勝ったら、またはもし四角が既に埋まったら、クリックを無視しすばやく返すためにhandleClick
を変えることができます:
handleClick(i) {
const squares = this.state.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
...
}
おめでとうございます!三目並べゲームが動くようになりました。そしてあなたはReactの基礎を知りました。おそらくあなたが本当のここでの勝者でしょう。
履歴を保存する
どんな過去の動きも見ることができるように、ボードの古い状態を再訪することを可能にしましょう。我々は既に各時間の動きを作る新しいsquares
配列を作っており、これは我々が簡単に過去のボードの状態を同時に保存できることを意味します。
状態の中のこのようなオブジェクトを保存する計画を立てましょう。
history = [
{
squares: [null x 9]
},
{
squares: [... x 9]
},
...
]
我々は、動きのリストを表示するための責任となるものとして、トップレベルのGame
コンポーネントを欲します。Board
のなかへSquare
から状態を引っ張ってくるのと同じように、再びGame
へBoard
から引っ張ってきましょう。そして我々はトップレベルで必要とするすべての情報を持ちます。
最初に、Game
のために初期状態を設定します。
class Game extends React.Component {
constructor() {
super();
this.state = {
history: [{
squares: Array(9).fill(null)
}],
xIsNext: true
};
}
...
}
それから、Square
とBoard
のために簡単につくられた変換のように、squares
をprops経由で取得しGame
によって特定される自身のonClick
propsを持つために、コンストラクタを削除しBoardを変更します。あなたは、四角がクリックされたことをまだ知ることができるように、それぞれの四角の場所をclickハンドラーに渡すことができます:
return <Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)} />;
Game
のrender
は多くの最近の履歴エントリを見ており、ゲームの状態を計算することを引き継ぐことができるでしょう。
const history = this.state.history;
const current = history[history.length - 1];
const winner = calculateWinner(current.squares);
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
}
...
<div className="game-board">
<Board
squares={current.squares}
onClick={(i) => this.handleClick(i)}
/>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{/* TODO */}</ol>
</div>
handleClick
は、新しい履歴配列を作るために新しい履歴エントリを連結することによって、新しいエントリをスタックに押し入れることができます。
handleClick(i) {
const history = this.state.history;
const current = history[history.length - 1];
const squares = current.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
history: history.concat([{
squares: squares
}]),
xIsNext: !this.state.xIsNext,
});
}
ここでのポイントは、BoardはただrenderSquare
とrender
のみを必要とする点です; 状態の初期化とクリックハンドラーは両方ともGameの中にあるべきです。
動きを表示する
これまでゲームで作られた以前の動きを表示しましょう。我々は、Reactエレメントは最上のJSオブジェクトであり、それらを保存したり周囲に渡したりできることを、最初に学びました。配列を構築する多くの普通の方法はあなたの配列のデータを写像することです。Gameのrender
メソッドでしてみましょう:
const moves = history.map((step, move) => {
const desc = move ?
'Move #' + move :
'Game start';
return (
<li>
<a href="#" onClick={() => this.jumpTo(move)}>{desc}</a>
</li>
);
});
...
<ol>{moves}</ol>
履歴のそれぞれのステップで、我々は、どこにも行かない(href="#"
)が間もなく実装するクリックハンドラを持つリンク<a>
とともにリストアイテム<li>
を作ります。このコードで、あなたはゲームのなかでつくられる動きのリストを見れるでしょう、下記の警告とともに。
警告: 配列またはイテレータの中のそれぞれの子は唯一の「key」propを持たなければなりません。「Game」のrenderメソッドをチェックしてください。
この警告が何を意味するかについて話しましょう。
キー
あなたがアイテムのリストを描画するとき、Reactはいつもリスト内それぞれのアイテムについてのいくつかの情報を保存します。もしあなたが、保存されることを必要とする状態を持つコンポーネントを描画するならば、そしてどのようにあなたのコンポーネントを実装するかにかかわらず、ReactはネイティブのViewを支援するために参照を保存します。
あなたがリストを更新するとき、Reactは何が変更されあかを決定することを必要とします。あなたはリストのアイテムを追加、削除、並べ替え、更新できたかもしれません。
<li>Alexa: 7 tasks left</li>
<li>Ben: 5 tasks left</li>
から
<li>Ben: 9 tasks left</li>
<li>Claudia: 8 tasks left</li>
<li>Alexa: 5 tasks left</li>
への移り変わりを想像してください。
人間の目には、AlexaとBenは場所が入れ替わり、Claudiaは追加されたように見えますが、Reactはただのコンピュータプログラムであり、あなたがするつもりであることはしりません。結果として、Reactはあなたに、それぞれのコンポーネントを兄弟から区別する文字列である、リスト内の各エレメントの特別な_key_プロパティを訪ねます。この場合、alexa
、ben
、claudia
は賢明なkeyかもしれません; もしアイテムがデータベース内のオブジェクトと比較するならば、データベースIDは大抵良い選択です。
<li key={user.id}>{user.name}: {user.taskCount} tasks left</li>
key
はReactによって予約されている特別なプロパティです(ref
に加えて、多くの拡張機能です)。エレメントが作られたとき、Reactはkey
プロパティを引っ張って取り、返されたエレメントに直接keyを保存します。たとえpropsの一部であるようにみえるかもしれないですが、this.props.key
で参照されることができません。Reactは子が更新されることが決定している間、自動的にkeyを使います; コンポーネントにとって自身のkeyを尋ねるための方法はありません。
リストが描画されたとき、Reactは新しいバー本の各エレメントを取得し、以前のリストのなかのkeyとマッチングするものを探します。keyが集合に追加されたとき、コンポーネントは作られます; keyが削除されたとき、コンポーネントは破棄されます。rendererを渡って状態を保ち続けるのために、keyがReactにそれぞれのコンポーネントの同一性について尋ねます。もしコンポーネントのkeyを変更すれば、それは完全に削除され新しい状態が作られます。
**あなたが動的なリストを構築するときはかならず固有のkeyを割り当てることが強く推奨されます。**もしあなたが使いやすい便利なkeyを持たないならば、あなたのデータを再構築することをよく考えてほしいです。
もしあなたが特定のkeyを持たない場合、Reactはkeyとして配列のindexを使うよう後戻りするよう警告するでしょう。もしあなたがこれまでにリストのエレメントを再整理したり、リストの下部のどこでもアイテムを追加や削除した場合は、それは正しい選択ではありません。はっきりとkey={i}
を渡すことで警告を静かにさせますが、多くの場合で推奨されず、同じ問題を持ちます。
コンポーネントのkeyはグローバルで唯一である必要はなく、目下の兄弟に関連して唯一であればいいです。
タイムトラベルを実装する
動きのリストのために、我々はすでにそれぞれのステップで唯一のIDを持ちます: 発生したときの動きの数字です。<li key={move}>
としてkeyを追加してkeyの警告は消えます。
どの動きのリンクをクリックしてもエラーが投げられ、これはjumpTo
が未定義であるからです。そのステップを我々が現在みているかを示すために新しいkeyをGameの状態に追加しましょう。最初に、stepNumber: 0
を初期stateに追加し、それから状態を更新するjumpTo
を持つようにします。
我々はまたxIsNext
を更新したいです。我々はもし動きの数字のindexが偶数であるならば、xIsNext
をtrueに設定します。
jumpTo(step) {
this.setState({
stepNumber: step,
xIsNext: (step % 2) ? false : true,
});
}
それからstepNumber
が追加されたことによって新しい動きが作られたときにstepNumber
を更新します: handleClick
のなかで状態を更新するためにhistory.length
を。
今、あなたは履歴のなかのステップから読み出すためにrender
を変更できます。
const current = history[this.state.stepNumber];
もしあなたが動きのリンクをクリックしたら、ボードは直ちに更新されそのとき見られたゲームを示します。あなたはまた新しいエントリが作られたボードをクリックして前に戻ることができるようにするために、現在のボードの状態を読み出したときにstepNumber
を知っているhandleClick
を更新したいでしょう(ヒント: トップにあるhandleClick
のhistory
から外部エレメントを.slice()
で切り取ることは簡単です)。
要約
今、あなたは三目並べゲームを作りました:
- 三目並べで遊んでみましょう
- Playerがゲームに勝ったときに示します
- ゲームの間の動きの履歴を保存します
- ゲームボードの古いバージョンを見るためにその時に戻ることをユーザーに許可します
良い仕事です!我々はあなたが今、Reactがどう働くかかなり掴んでいると感じます。
もしあなたが他の時間を持ち、新しいスキルを準備したいならば、あなたができるであろう、難易度が増える順序で並べられた、改善のための幾つかのアイデアがあります:
- 「6」にかわって「(1, 3)」のフォーマットで動きの場所を表示する
- 動きのリストで現在選択されているアイテムを太字にする
- ハードコーディングするかわりに四角を作るための2つのループを使ってBoardを書き換える
- 昇順か降順で動きをソートするトグルボタンを追加する
- 誰かが勝ったときに勝ちの要因である3つの四角をハイライトする
翻訳ここまで
チュートリアルのJacaScriptコード
本チュートリアルで最終的に完成したJavaScriptのコードは下記となります。
Starter CodeのJSの欄に貼り付ければ動くはずです。
注: 要約にある改善点については反映されていません。
function Square(props) {
return (
// クリックされたときに親のBoardから渡されたonClickを実行する
// 親から渡されたvalueを表示する
// 親から渡されたobjectはthis.props.*でアクセスできる
<button className="square" onClick={() => props.onClick()}>
{props.value}
</button>
);
}
class Board extends React.Component {
renderSquare(i) {
// Squareを描画し、Squareには親のGameから渡されたvalueとしてsquareを、そしてonClickを渡す
return <Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)} />;
}
render() {
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 {
constructor() {
super();
// 状態を初期化
this.state = {
// ゲームの履歴の状態。ゲーム盤と同じ9の配列を持ち、クリックされるたびに9の配列が増えていく
history: [{
squares: Array(9).fill(null)
}],
// 現在がXの番である場合はtrue
xIsNext: true,
// 現在のstep
stepNumber: 0
};
}
// Board -> Squareに渡される。クリックされたときの処理を定義
handleClick(i) {
// 履歴の配列を取得
const history = this.state.history;
// 現在のstep番号から現在のゲーム版のOXの状態を取得
const current = history[this.state.stepNumber];
// Square配列をコピーして取得
const squares = current.squares.slice();
// ゲームの勝者が決まっていたり、すでにクリックされてOXが表示されている場合は何もしない
if (calculateWinner(squares) || squares[i]) {
return;
}
// クリックされたSquareに現在のPlayerに応じてXやOを設定する
squares[i] = this.state.xIsNext ? 'X' : 'O';
// 状態を更新する
this.setState({
// history配列の末尾に新しいsquareを追加する
history: history.concat([{
squares: squares
}]),
// 次のPlayerの番になるので反転させる
xIsNext: !this.state.xIsNext,
// stepを1増やす
stepNumber: this.state.stepNumber + 1,
});
}
// クリックしたstep時のゲームの状態に戻る
jumpTo(step) {
this.setState({
// ゲームstepを更新する
stepNumber: step,
// Playerの順番を更新する
xIsNext: (step % 2) ? false : true,
// クリックしたstepより後の履歴は不要なので切り捨て、historyを更新する
histroy: this.state.history.slice(0, step),
});
}
render() {
// 履歴の配列を取得
const history = this.state.history;
// 現在のstep番号から現在のゲーム版のOXの状態を取得
const current = history[this.state.stepNumber];
// 現在のSquare配列からゲームの勝者がいるかを計算する
const winner = calculateWinner(current.squares);
let status;
// 勝者がいる場合
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
}
// 履歴一覧を表示するためのmovesを作成する
// moveには0から履歴分の数値が逐次はいっている
const moves = history.map((step, move) => {
const desc = move ?
'Move #' + move :
'Game start';
return (
<li key={move}>
<a href="#" onClick={() => this.jumpTo(move)}>{desc}</a>
</li>
);
});
return (
<div className="game">
<div className="game-board">
<Board
squares={current.squares}
onClick={(i) => this.handleClick(i)}
/>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{moves}</ol>
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(
<Game />,
document.getElementById('container')
);
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;
}
さいごに
一通りチュートリアルをしてみて、関数やクラスで構成されている点が分かりやすかったです。ただ、状態やクリックの処理を親に持たせておき子に渡している点が、どこまで親に持たせておけばいいかなど悩みそうだと感じました。