これは
- 業務で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はtrueorfalseで値を持っており、クリックイベントで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)内に下記を追加することで「決着がついている」「すでに盤面が埋まっている」場合、クリックでXorOの更新をしないようになっています
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チュートリアルすごい
まだまだ基礎が学べただけだと思うので、これからも勉強を続けて実務でコード書けるように頑張ります!
(そしていつかこのコードを改造&リファクタしたい)
























