これは
- 業務でRails(Ruby)を書いている人がフロントの勉強としてReactチュートリアルをやってみたMEMOです
- 書き方悪いところやリファクタリングできるところが山ほどあると思いますが、多めに見てくださるか優しくご指摘頂けると助かります。。(間違っているところは容赦無くお願いします)
この時のわたし
- Rails(Ruby)でバックエンドの実装に携わる
- 業務でフロント連携することが多く、自分でもさわれたらいいなーと思った
- とりあえずJS(ES6)の基礎の基礎だけ学んだ
- Rubyの他にちゃんと触ったことある言語はHTML・CSS・JS(jQuery)くらい
リポジトリ
Reactチュートリアルとは
- Reactの公式チュートリアル
- Reactだけで三目並べ(いわゆるマルバツゲーム)を作れる
- 今現在日本語版もできており大変ありがたい
公式の前提知識という項目に
- HTML と JavaScript に多少慣れていることを想定
- 関数、オブジェクト、配列、あるいは(相対的には重要ではありませんが)クラスといったプログラミングにおける概念について、馴染みがあることを想定
- また ES6 という JavaScript の最近のバージョンからいくつかの機能を使用していることにも注意
とあるので、その辺りについて軽く予習した方が効率が良いかと思います
チュートリアルの準備
- チュートリアルを始める前にという項目があるので、それに沿って準備をする
- 手順や説明など詳しくチュートリアルに書いてある部分は割愛
- 詰まったところやわからなくて調べたところをメインに残している
- ブラウザでコードを書く
- ローカルの開発環境で作る
どちらか選べますが、わたしはローカルの開発環境で作る方で進めました
- npx?
- npm 5.2 から利用できるパッケージランナーツール
- npm?
Node Package Manager
- Nodeを管理するツール
ローカルで開発できるようにし、さらに手順に沿って進めると
- 三目並べ用の枠ができた!
この状態でRailsと同じようにターミナルから
nps start
とするとlocalhost:3001
でブラウザで表示できるようになります
- nps?
npm-package-scripts
- https://www.npmjs.com/package/nps
概要
これで準備が終わったので次は概要を学んでいきます
React とは?
- React?
- JavaScriptのライブラリ
- コンポーネントと呼ばれる部品を組み合わせて複雑なUIを実現できる
-
props
=properties
の略 -
render
で表示するビューの階層(説明書き)を返す - Reactがその説明を読み取って画面に描画する
-
Babel
はES6
->ES5
のトランスパイラ
スターターコードの中身を確認する
最初に作ったindex.jsを見てみるとReact.Componentを
継承したクラスが3つありました
-
extends
で継承(ES6) - ex)
Square
クラスがReact.Component
クラスを継承(extends)している
class Square extends React.Component
-
Game
クラスはこの時点でまだ実装されていない
データを Props 経由で渡す
チュートリアル通りにコードを書き換えていくとこんな感じで表示されるようになりました
- React では、親から子へと
props
を渡すことで、アプリ内を情報が流れていく - ここでは
Board
(親)->Square
(子)へprops
としてvalue
という名前の値をSquare
に渡している
インタラクティブなコンポーネントを作る
this の混乱しやすい挙動を回避するため、この例以降ではアロー関数構文をつかってイベントハンドラを記述
- アロー関数は親の
this
を継承する(ES6) -
onClick
プロパティに渡しているのは関数なのでReactはクリックされるまでこの関数を実行しない
これでクリックされた時のイベントを作れたので今度はクリックされたらX
が表示されるようにします
- Reactコンポーネントはコンストラクタで
this.state
を設定することで、状態を持つことができるようになる - 現在の
Square
の状態をthis.state
に保存して、マス目がクリックされた時にそれを変更することができる
Square
のconstructer
内でthis.state
としてvalue: null
を定義し、
render
する時に渡されたprops
ではなくthis.state
とすることで初期値のvalue: null
が反映され↓の状態に戻りました!
クリックイベントでalert
を表示していたところを
() => this.setState({ value: 'X' })
とすることでstate
の値をX
に更新して表示できるようになりました!
-
Square
のrender
メソッド内に書かれたonClick
ハンドラ内でthis.setState
を呼び出すことで、Reactに<button>
がクリックされたら常に再レンダーするよう伝えているため -
setState
をコンポーネント内で呼び出すと、React はその内部の子コンポーネントも自動的に更新する
Developer Tools
- ChromeにReactのDeveloperツールを入れよう
- 下記リンクからインストールできる
React Developer Tools - Chrome ウェブストア
ゲームを完成させる
ここからゲームにしていくために下記を実装してきます
完全に動作するゲームにするためには、盤面に “X” と “O” を交互に置けるようにすることと、どちらのプレーヤが勝利したか判定できるようにすることが必要
State のリフトアップ
- 親コンポーネント内で共通の
state
を宣言することで子コンポーネント同士を兄弟のようにでき、互いにやりとりできるようになる - 親コンポーネントは
props
を使って子に情報を返せる(ここではvalue={i};
の部分)- こうすることで子コンポーネント同士、親子同士で常に同期されるようになる
this.state = {
squares: Array(9).fill(null),
};
// Array(9) -> 9この要素を持った配列を作る, fill(null) -> 配列の要素を全てnullで埋める
【JavaScript】配列を同じ値で埋めたり、連続した値を設定する - Qiita
こうするとBoard
のstate
として
[
X, O, null,
X, null, O,
null, X, null
]
みたいな配列を持つことになります
- ここを更新してその値を子に伝えることで子の
state
を管理することができる - 子がクリックされた時に親の
state
を変更したいが、できないので親の関数を呼んでstate
を更新するようにする
これで子が自身のstate
を管理しなくなったので子のconstructor
はいらなくなりました
handleClick()
を定義していないと怒られたので定義し、クリックされたらsquares
の値を更新してやるとまたX
が表示されるように!
handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = 'X';
this.setState({squares: squares});
}
// arr.slice(begin[, end]); -> beginを省略するとindex0からになる
.slice() | JavaScript 日本語リファレンス | js STUDIO
イミュータビリティは何故重要なのか
- イミュータブルなオブジェクトは変更が簡単
- 参照しているイミュータブルなオブジェクトが前と別のものであれば、変更があったということ
- Reactにおいてはコンポーネントをいつ再
render
すべきなのか決定しやすくなる
関数コンポーネント
- 関数コンポーネント
-
render
メソッドだけを有して自分のstate
を持たないコンポーネントを、よりシンプルに書くための方法
-
Square
クラスは自分でstate
の管理をしなくなってconstructor
を削除したので関数コンポーネントに置き換えられます
手番の処理
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.state.xIsNext
はtrue
orfalse
で値を持っており、クリックイベントでstate
の真偽値を反転させます
そしてtrue
ならXをfalse
ならOにsquares[i]
を更新するようにします
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
xIsNext: true, // ここで次の手がOかXかの状態を管理
};
}
handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext, // クリックしたら真偽値を反転させてstateを更新する
});
}
ゲーム勝者の判定
勝ち負け判定ができる関数が用意されているのでそれを使用します
- 勝ったプレイヤーがいたら
X
かO
を、いなかったらnull
が返る
render() {
const winner = calculateWinner(this.state.squares);
let status;
// winnerがnullじゃなかったらどちらかが勝ちで X or O が返ってきている
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
}
また、handleClick(i)
内に下記を追加することで「決着がついている」「すでに盤面が埋まっている」場合、クリックでX
orO
の更新をしないようになっています
if (calculateWinner(squares) || squares[i]) {
return;
}
最初、なんでsquares[i]
?って思ってましたが、すでにX
が入っているところがO
に更新されたらおかしいですもんね
よく考えたら当たり前のことでした…
タイムトラベル機能の追加
ゲームの進行状況を巻き戻したり、盤面をリセットしたりする機能を追加していきます
着手の履歴の保存
squares
の配列を着手があるたびhistory
という名前の別の配列に保存していくことで履歴として保持することができます
State のリフトアップ、再び
Gameクラスで全ての値を管理します
クリックイベントは
-
square
でどの盤面がクリックされたか判断する -
Board
からクリックされた盤面の情報をGame
に伝える -
GameのhandleClick
で値を更新する -
handleClick
で盤面の情報をhistory
に追加して記録していく
過去の着手の表示
// mapの第二引数はindex(何回目か)
const moves = history.map((step, move) => {
const desc = move ?
'Go to move #' + move :
'Go to game start';
return (
<li>
<button onClick={() => this.jumpTo(move)}>{desc}</button>
</li>
);
});
Array.prototype.map() - JavaScript | MDN - Mozilla
key を選ぶ
リストをrender
する際Reactはkey
で差分を測る(key
= Reactの予約語)
<li key=重複しない値>リストの中身</li>
<!-- コンポーネントとその兄弟の間で一意であればOK(グローバルで一意の必要はない) -->
- もし以前になかった
key
がリストに含まれていれば、Reactはコンポーネントを作成する - もし以前のリストにあった
key
が新しいリストに含まれていなければ、Reactは以前のコンポーネントを破棄する - もし2つの
key
がマッチした場合、対応するコンポーネントは移動する -
key
はそれぞれのコンポーネントの同一性に関する情報をReactに与え、それによりReactは再レンダー間でstate
を保持できるようになる - もしコンポーネントの
key
が変化していれば、コンポーネントは破棄されて新しいstate
で再作成される - コンポーネントが自身の
key
について確認する方法はない
以上のことから動的なリストを構築する場合は正しいkey
を割り当てることが強く推奨される
const history = this.state.history.slice(0, this.state.stepNumber + 1);
// array.slice(begin, end); -> 第二引数は抜き出す要素の終わりを指定する
これにより任意の範囲でhistoryを取り出す(まきもどした時点以降の履歴を削除する)
Array.prototype.slice() - JavaScript - MDN - Mozilla
拡張課題
ここからが本題!ということでそれぞれ考えながら実装してみました
拡張課題は全部で6つあります
- 履歴内のそれぞれの着手の位置を (col, row) というフォーマットで表示する。
- 着手履歴のリスト中で現在選択されているアイテムをボールドにする。
- Board でマス目を並べる部分を、ハードコーディングではなく 2 つのループを使用するように書き換える。
- 着手履歴のリストを昇順・降順いずれでも並べかえられるよう、トグルボタンを追加する。
- どちらかが勝利した際に、勝利につながった 3 つのマス目をハイライトする。
- どちらも勝利しなかった場合、結果が引き分けになったというメッセージを表示する。
1. 履歴内のそれぞれの着手の位置を (col, row) というフォーマットで表示する。
着手した盤面のcol,rowを一緒に保存したいというとで、
Go to move #1(col: 1, row: 3)
みたいな感じで表示したいなーと考えました
まずどうやってcolとrowの出すかだけど、ここがめっちゃ悩んだ気がします。。
図を書いて考えて
// col
i % 3 + 1;
//row
i / 3(の商) + 1;
とりあえず、これで
ということで、rowは商の部分だけ欲しいので整数にするメソッドを探した
実数を整数に丸める4パターン(JavaScript おれおれ Advent Calendar 2011 – 7日目) | Ginpen.com
与えられた値を上回らない最大の整数、つまり同じか小さい整数を返します。
//row
Math.floor(1 / 3) + 1;
次にコードのどこに書くか、ですが
表示するところはmove
のところなので、コードを見てみると
Game
内のrender
部分でhistroy
をmap
して繰り返し表示しているから、history
にsquares
と共に持たせるのが良さそうと思い
まずhistory
の中にcol
とrow
の情報を持たせました
this.setState({
history: history.concat([{
squares: squares,
// ここにさっき出した計算式を入れた
col: (i % 3) + 1,
row: Math.floor(i / 3) + 1,
}]),
stepNumber: history.length,
xIsNext: !this.state.xIsNext,
});
で、render
内ではmap
で順番に取り出したstep
のなかのcol
とrow
なので一緒に表示させてあげます
const moves = history.map((step, move) => {
const decs = move ?
// historyで持ってるcolとrowの値を表示する
`Go to move #${move}(col: ${step.col}, row: ${step.row})`:
`Go to start`;
return (
<li key={move}>
<button onClick={() => this.jumpTo(move)}>{decs}</button>
</li>
);
});
文字列はテンプレートリテラルを使って書き換えてます!(かきやすい!)
これで、できました!!
2.着手履歴のリスト中で現在選択されているアイテムをボールドにする。
着手履歴を表示しているところは上記と同じくGame
内のrender
部分なので、条件で表示を変えるif文を使えば良さそうです
- JSXでのif文の表示はこちらを参考
「現在選択されているアイテム=最新の履歴」と考えて、
- もし
move
がhistory
の中身の数だったら- 文字をボールドにして表示する
- そうじゃなかったら普通に表示する
とすれば良さそう!
history.length
はstate
の中のstepNumber
で定義されているのでそれを使えそうなので、return
の中身を
return (
<li key={move}>
<button onClick={() => this.jumpTo(move)}>
{ move === this.state.stepNumber ? <strong>{decs}</strong> : decs }
</button>
</li>
);
こんな感じで書き換えました!
条件分岐の処理は複雑じゃないのでインラインで三項演算子を使っています
3.Board でマス目を並べる部分を、ハードコーディングではなく 2 つのループを使用するように書き換える。
Board でマス目を並べる部分は繰り返しが2回あります
- 1つは
<div></div>
を3回繰り返すところ - もう一つはそのなかで
{this.renderSquare(i)}
を3回繰り返すところ
map
をネストすればやりたいことができそう
また、チュートリアルの最後に書いてあったように
動的なリストを構築する場合は正しい key を割り当てることが強く推奨される
なのでそれぞれにkey
を渡す必要がありそうです
history
表示させているとこもmap
のindex
をkey
にしているので、こちらも同じようにしてみます
render() {
const boardSquares = [[0, 1, 2], [3, 4, 5], [6, 7, 8]];
return (
<div>
{
boardSquares.map((rowSquares, i) => {
return(
<div className="board-row" key={i}>
{
rowSquares.map((square) =>{
return(
this.renderSquare(square)
)
})
}
</div>
)
})
}
</div>
);
}
}
これでうまく表示されたけど、ちょっとパワープレイ感あるのでどうにかリファクタしたいですね。。
あとrenderSquare
の部分にもkey
持たせなかったら怒られてたので追加
renderSquare(i) {
return <Square
value={ this.props.squares[i] }
onClick={ () => this.props.onClick(i) }
key={i}
/>;
}
boardSquares
の部分をいい感じに配列作れるようにできれば…?と思ってちょっとチャレンジしてみようと思い
どうやったらいいかちょっと考えました
- まず、盤面の一辺のマス目を決める
- 3
- 正方形の盤面なので、マス目の数は一辺の2乗
- 9
- マス目の数を0から始めてマス目分だけ順番に配列にする
- [0, 1, 2, 3, 4, 5, 6, 7, 8]
- その配列を一辺の数ごとに配列でくくる
- [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
そしてできたのがこちら
ちょっと長くなったけど、boardLines
の数を変えると盤面がいい感じに変わるようになりました!
render() {
// 一辺が何マスの盤面を作るか
const boardSideLength = 3;
// boardSideLengthから盤面のsquareの数を出して0から始まる配列にする
const boardSquares = [...Array(boardSideLength ** 2).keys()];
// 配列からrowごとのまとまりを作る
const boardSquaresList = boardSquares.reduce((square, i) => {
const lastSquare = square[square.length - 1];
if (lastSquare.length === boardSideLength) {
square.push([i]);
return square;
}
lastSquare.push(i);
return square;
}, [[]]);
return (
<div>
{
boardSquaresList.map((rowSquares, i) => {
return(
<div className="board-row" key={i}>
{
rowSquares.map((square) =>{
return(
this.renderSquare(square)
)
})
}
</div>
)
})
}
</div>
);
}
}
-
Range
で配列にするっぽいのは下記を参考に - スプレッド演算子で
boardSideLength ** 2
の数だけ展開してObject.keys()
でindexを取得しているのかな?わかりやすくて良かったです
(Rubyで書くとこんなかなぁと思いつつ)
board_squares = Array(0..(board_side_length ** 2)-1)
- そこから配列を加工するのは下記を参考に
-
reduce
の使い方まだ慣れないなーと思いつつなるほどなーと思った - 初期値を
[[]]
にして、lastSquare.length === boardSideLength
になったらまた新しい配列に追加する、とか全然考えつかなかった、、
そんなわけで盤面を4×4にしたり
10×10にしてもちゃんとrender
されるようになりました!
(なお当然だがゲームとしては成り立っていないw)
勝ち負け判定とcol,rowの計算で3って置いてるのどうにか直したら五目並べ的なゲームにできそう感はあるけど難しそうですね(勝ち負け判定のところが特に)
4.着手履歴のリストを昇順・降順いずれでも並べかえられるよう、トグルボタンを追加する。
だんだん考えることが多くなってきたので、順を追いながら一つずつ解決してみることにしました
- とりあえずボタンを作成したい
- どこに?
-
Game
の表示部分、status
の下あたり
-
const orderBottun = `ASC <-> DES`; // buttonの中身
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>
<div><button>{orderBottun}</button></div> // buttonを追加
<ol>{moves}</ol>
</div>
</div>
);
- ステータスに昇順か降順かの値を持たせる(
true
/false
で切り替え?)-
isAsc
=true
(昇順)/false
(降順)
-
- どこで?
-
Game
のstate
-
- 降順での表示方法は?
-
moves
を逆にすれば良さそう -
isAsc
がfalse
だったらmoves.reverse()
にする!
-
constructor(props) {
super(props);
this.state = {
history: [{
squares: Array(9).fill(null),
}],
stepNumber: 0,
xIsNext: true,
// いったんfalseで置いて降順になるか確認
isAsc: false,
};
}
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>
<div><button>{orderBottun}</button></div>
<ol>
{ this.state.isAsc ? moves : moves.reverse() } // falseなら逆で並べる
</ol>
</div>
</div>
);
2.着手履歴のリスト中で現在選択されているアイテムをボールドにする。 の時とやってることは同じですね
逆になっていますね!
- ボタンをクリックしたら
isAsc
のtrue
/false
が逆になるようにする - まずクリックした時のイベントを作る
-
jumpTo
を参考に押されたらstate
を更新する ->isAsc
のtrue
/false
の入れ替え
-
handleOrder() {
this.setState({
isAsc: !this.state.isAsc,
});
}
- ボタンに
onClick
イベントを作る
<button onClick={() => this.handleOrder()}>
{orderBottun}
</button>
- ついでに今どっちの並び順なのかボタンでわかるようにしてみた
const orderBottun = this.state.isAsc ? '↑OLD NEW↓' : '↑NEW OLD↓';
どっちが新しい手順なのかボタンでわかるようになりました
5.どちらかが勝利した際に、勝利につながった 3 つのマス目をハイライトする。
勝った時にどのラインで勝ったのかがわかれば、該当のマスだけ色をつける、といったことができそうです
- 勝ち判定がでたときに、一緒に該当のラインを返すようにする
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する
return { player: squares[a], line: [a, b, c] };
}
}
return null;
- これで勝ったときに勝ちプレイヤーとラインが入った
Object
が返ってきた
-
winner
表示のところでObject
からplayer
を取り出して表示する
const winnerInfo = calculateWinner(current.squares);
let status;
if (winnerInfo) {
status = `Winner: ${winnerInfo.player}`;
} else {
status = `Next player: ${this.state.xIsNext ? 'X' : 'O'}`;
}
これでちゃんと表示されますね
- 次に、色をつけるところ
-
line
から勝ち判定のマスを引っ張ってきて色をつければいい - どこに?
-
Square
の<button> ... </button>
-
- いったんCSSにハイライトの設定を作って反映させてみる
.highlight-color {
background: #ff0000;
}
function Square(props) {
return (
<button className="square highlight-color" onClick={props.onClick}>
{props.value}
</button>
);
}
できました!
-
className
には続けてCSSを指定してあげればOKだった - あとはこの
highlight-color
を条件分岐で表示させられたら良さそう
とはいえ、どこからこの情報を持って来ればいい?どういう条件で表示する?ってとろこで少し悩みました
- データはprops経由で親から子へ渡る
- チュートリアルを見返すと
Board の renderSquare メソッド内で、props として value という名前の値を Square に渡すようにコードを変更します:
- もう一回コードをよく見てみると
-
Square
で表示しているのはprops.value
- こいつはどこの値かというと
Board
内の 内のvalue={ this.props.squares[i] }
-
class Board extends React.Component {
renderSquare(i) {
return <Square
value={ this.props.squares[i] }
onClick={ () => this.props.onClick(i) }
key={i}
/>;
}
...
- じゃあこの
this.props.squares[i]
はどこから来てるんだ -
Game
内の 内のsquares={current.squares}
っぽい
return (
<div className="game">
<div className="game-board">
<Board
squares={current.squares}
onClick={(i) => this.handleClick(i)}
/>
...
コードを読み返してわかったこと:
-
Game
->Board
->Square
とprops
が渡ってるので -
Game
内の<Board ... />
->Board
内のrenderSquare(i)(<Square ... />)
->Square
の<button>
と値を渡してやれば良さそう
というわけで
- 「もし、勝ったラインの配列に
Square
のi
があったら、色をつける」みたいな条件にするとしたら-
Game
->Board
へは勝ったラインの配列
を -
Board
内のrenderSquare(i)
で渡ってきた配列にi
が含まれているか(true
/false
)を -
Square
で渡って来た値がtrue
だったら色をつける、とする
-
という方針で進めることにしました
1. Game
-> Board
へ勝ったラインの配列
を渡す
- 勝ち判定を
if (winnerInfo) { 〜
の部分でしているので、勝った場合winnerInfo.line
を入れる部分を作ってやる
let status;
let winLine = []; // 追加
if (winnerInfo) {
status = `Winner: ${winnerInfo.player}`;
winLine = winnerInfo.line; // 勝ち判定が出た場合ここに勝ちにつながったlineを入れる
} else {
status = `Next player: ${this.state.xIsNext ? 'X' : 'O'}`;
}
return (
<div className="game">
<div className="game-board">
<Board
squares={current.squares}
onClick={(i) => this.handleClick(i)}
winLine={winLine} // ここでpropsとしてBoardにwinLineを渡す
/>
2. Board
内のrenderSquare(i)
で渡ってきた配列にi
が含まれているか(true
/false
)を渡す
- 配列の中に含まれているかどうか、を調べるのは下記を参考に
renderSquare(i) {
return <Square
value={ this.props.squares[i] }
onClick={ () => this.props.onClick(i) }
isHighlight={ this.props.winLine.includes(i) } // 渡って来た配列にiが含まれているかどうか
key={i}
/>;
}
3. Square
で渡って来た値がtrue
だったら色をつける
<button
className={
props.isHighlight ? `square highlight-color` : `square` // trueなら highlight-color を反映する
}
onClick={props.onClick}
>
なんとかできました!!!
6.どちらも勝利しなかった場合、結果が引き分けになったというメッセージを表示する。
これもまずさらっと実装方針を考えてみます
- 勝ち負け判定に
Draw
を追加してstatus
の部分で条件分岐を追加して表示させてあげる - 勝ち負け判定は
calculateWinner(squares)
でやっていてsquaers
には盤面の情報が入っている -
squaers
のnull
がなくなった時(盤面が全て埋まった時)がDraw
ということとする
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 { player: squares[a], line: [a, b, c] };
}
}
// ここでDraw判定
if (!squares.includes(null)) {
return 'draw';
}
return null;
}
これで
- どちらかが勝った時
{ player: 'X' or 'O', line: [a, b, c] }
- 引き分けの時
'draw'
- 決着が付いていない時
null
が返ってくるようになりました
条件分岐の順番を少し並び替えて下記のようにしたら…
let status;
let winLine = [];
if (winnerInfo === null) {
status = `Next player: ${this.state.xIsNext ? 'X' : 'O'}`;
} else if (winnerInfo === 'draw') {
status = `Draw`;
} else {
status = `Winner: ${winnerInfo.player}`;
winLine = winnerInfo.line;
}
引き分け時にDraw
と表示できました!!
これで全部の追加課題が終了
お疲れさまでした=^o^=
おわりにちょっとした感想など
- もっとリファクタ&改造できるところはあると思うが、とりあえずちゃんと表示されて動くものを作れるのが目標だったので、達成できてよかった
- class間の(props)値の受け渡しの流れがまだふんわりとしかつかめていないのでので復習したい
- でも動くもの作れたのはすごく楽しい
- 情報を管理できるstateすごい…Reactだけで簡単なゲームが作れちゃうんだなぁ
- 基本的なJS(ES6)がわかればできるのでReactチュートリアルすごい
まだまだ基礎が学べただけだと思うので、これからも勉強を続けて実務でコード書けるように頑張ります!
(そしていつかこのコードを改造&リファクタしたい)