React Tutorial やってみた
これは 琉大 Advent Calendar 2020 13日目の記事です。
概要
こんにちは、りゃまです。
今回は React Tutorial を雑にやってみたので実況プレイ的な感じで書いていきます。
やること
React Tutorial を通してみるだけなのでへんなことはしません。
ゴールとしては
「ほぇ〜〜こういうやつね、完全に理解したね」
と言えること。
フロント知識皆無で大人気 React くんのことも名前しか知らない程度なので指摘コメント大募集です。
この記事の記述内容は次のようにする予定です。
- 公式が詳しく説明してるところは流したりします。
- 動作的に関わらないところも説明しない箇所があります。
取り組む方は公式読みながらやってみてください。
取り組み方
- 参考:React Tutorial
- 環境:Docker for Mac
React Tutorialに沿って取り組みます。
ブラウザで実行できる環境も提供されていましたが、今後手元で React 環境使って行きたかったので雑に Docker にします。
最終的に動いたものは一応githubにありますが、公式でもコード見られるのでこっち見る必要なさそうです。
React Tutorial 開始
どうやらマルバツゲームを作る内容になっているようです。
環境構築からゲーム基盤づくり、細かな機能追加の流れでやっていきましょう。
環境構築
やるぞ!となって最初にぶつかる環境構築の壁。今回も一番時間かかった気がするような。 こういうところもちゃんと勉強しないとなぁ。
Docker書き書き
まずは Docker まわりを書きます。
今回は React 単体で良いですが、今後どこかとつなぎたいので docker-compose も一緒に書いていきます。
(知識がなさすぎて不足部大量にあると思うので誰か教えて下さい)
目指すディレクトリ構成はこんな感じ。app の下に React app が配置されるイメージです。
ReactTutorial ├── app ├── docker-compose.yml └── dockerfile
node の image を使って、お気持ち程度に Working directory を設定してあげました。
FROM node:15.3.0-alpine3.12 WORKDIR /app
素晴らしき Docker、これで node 環境は多分オーケー。
続いて docker-compose.yml を。
version: '3' services: app: build: . volumes: - ./app:/app ports: - 3000:3000 command: sh -c "yarn install && yarn start"
これで最低限の環境ができたはず。
今後サーバーサイドいい感じに追加していけば、docker-compose でまとめて管理できるでしょう。
build から create-react-app
Docker まわり書き終えたので、続いて build。
> docker-compose build
それっぽく build されます。
ここから React のファイルを作っていきます。
公式が用意してるセットアップ説明に沿ってやっていきましょう。
こちらで公式がいい感じに説明してくれてますが、どうやら create-react-app というもので生成できるらしいです。
node のパッケージ管理は多分今回何でも良いんですが、なんとなく yarn 使ってみます。
> docker-compose run --rm app yarn create react-app .
これでコンテナの /app に react app が生成され、volume でつながってるローカルの app directory にも反映されました。
これで React が立ち上がるはず……!
> docker-compose up
localhost:3000 にアクセス……
完璧ですね。
まぁ今回は Tutorial ように用意されているコードに置き換えるので、説明に沿って src の下を全削除、サンプルコードを配置します。
ここまでできれば準備完了。 こんな画面が表示されて Tutorial を進めていけそうな予感がします。
コード確認
サンプルで与えられたコードの中身を見ておきましょう。
先程用意した src/index.js を確認。どうやら3つの class で構成されているようです。
React では Component というものを用意して、それを呼び出すことで描画するみたいです。
Component は関数や class で用意することができ、class を Component にするには React.Component を継承させるようです。
そして今回すべての class が React.Component を継承していています。
- Square class
- マス1つ分
- 現状 button を描画するようになっている
- Board class
- おそらくマス目全体の class
- 9個の Square を呼び出してあのマス目を描画するっぽい
- Game class
- 一番上
- Board を呼び出したりするっぽい
Game を呼び出すとマルバツゲームが表示されるようになるみたいですね。
ゲーム基板づくり
まずはボードをクリックするとそこにXが置かれるようなところから作っていくようです。
とりあえず描画
まずは四角の中に数値が表示されるようにしてみましょう。
Props とやらを使うことで Component に値を渡すことができるそうです。引数みたいなものかな。
公式に従って Board にコードを追記。
// 変更前 <Square /> // 変更後 <Square value={i} />
なるほど、こうやると Square 内で value の中身を取り出せるんですね。これが props か。
早速取り出す側も書いてみましょう。Square に追記。
// 変更前 <button className="square"> </button> // 変更後 <button className="square"> {this.props.value} </button>
ほぇ〜、this.props.value
でさっきのやつが取り出せると。
class が props を持っていて、Component の呼び出し時に書いたものが入るのかな。
これで数値が描画できました。
クリックで描画
マルバツゲームなので選んだ場所に印をつけられるようにしたいです。
そこで button に onClick を記述し、関数を渡すようです。 更に何やら state というものを使っていくみたいです。
とりあえず公式に従って Square に追記。
// constructor を作成 constructor(props) { super(props); this.state = { value: null, }; }
// onClick を追加して表示も state を使用 <button className="square" onClick={() => this.setState({value: 'X'})} > {this.state.value} </button>
描画する値を props から state に変更し、onClick によって state の値が変化することで表示内容が変わるという仕組みでしょう。
これでクリックした場所に X がつけられるようになりました。 最低限の動きが整い、ゲーム基板の完成です。
ゲーム完成まで
現状だと X しか置くことができません。 これを O X 交互におけるようにして、勝敗判定をすることでゲーム完成となります。
盤面情報の管理
今の設計だと勝敗判定がやりづらいでしょう。 なんたって何が表示されているかは9個の Square 単体がそれぞれ管理してしまっています。
そこで state の値を Board で管理するようにします。この子から親への管理移行をリフトアップというみたいですね。
先程 Square に作成した constructor を Board に移します。 そして Board は9個分の情報を管理したいので、先程の value は必然的に Array で管理することになりました。
class Board extends React.Component { constructor(props) { super(props); this.state = { squares: Array(9).fill(null), }; } // 以下略
そして Square を描画するときにこの squares からデータを引っ張り出すようにすれば完璧です。
renderSquare(i) { // 変更前 return <Square value={i} />; // 変更後 return <Square value={this.state.squares[i]} />; }
このままだと Square から Board の情報を更新できないのでクリックしてもだめですね。 そこで Square に定義した関数を呼び出せるように props で渡しておきます。
// Boardで <Square value={this.state.squares[i]} onClick={() => this.handleClick(i)} /> // Square で <button className="square" onClick={() => this.props.onClick()} >
これでこれから Board で定義する handleClick()
を Square のクリックで呼び出せるようになりました。
handleClick()
はこんな感じになります。
handleClick(i) { const squares = this.state.squares.slice(); squares[i] = 'X'; // X で固定されてる状態 this.setState({squares: squares}); }
現状クリックされたら X が入るようになってますね。
動作は先程と変わりませんが、盤面情報を完全に Board で管理できるようになりました。 リフトアップ完了です。
マルバツ交互に
のこりは O X 交互にする部分。
単純な実装で行きましょう。boolean で True なら X、False なら O、一回置くごとに反転してしまえばいいです。
state に情報を追記します。
this.state = { squares: Array(9).fill(null), xIsNext: true, // こいつ追加 };
そして先程のhandleClick()
で毎回反転させます。O X は三項演算子でまとめてます。これぐらいだと便利ですね。
handleClick(i) { const squares = this.state.squares.slice(); squares[i] = this.state.xIsNext ? 'X' : 'O'; // スマート、これで X 固定じゃないね this.setState({ squares: squares, xIsNext: !this.state.xIsNext, // not にしてて毎回反転されるね }); }
これで O X 交互に置くことができるようになってます。 ついでに Board で誰の手番なのかも出しておきます。
const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
完成
あとは勝敗判定ができれば完成です。
まぁマルバツゲームの勝敗判定は筋肉で縦横斜めの8パターン揃っている箇所がないかを判定してしまえばわかるので、公式のやつをコピペするなりして実装します。
ただ今のままだと相手の印の上に上書きできたり勝敗付いてるのに続けられちゃったりと色々すごいのでそこの処理まで実装。
印をつける処理はhandleClick()
となっているので、そちらでわちゃもちゃします。
勝敗がついているか、もしくはすでに印がある場合は return してしまえば解決しますね。
handleClick(i) { const squares = this.state.squares.slice(); // calculateWinner() は勝者がいなければ null を返すので、その場合 false // squares には O、X、null が入ってるので、何もなければ false if (calculateWinner(squares) || squares[i]) { return; }
これで完成。楽しくマルバツゲームで遊ぶことができます。
おわり
ほぇ〜〜こういうやつね、完全に理解したね。
サクッと React を体験しながらマルバツゲームを作成しました。
Component の扱い、props・state を用いたデータのやり取りなどの空気感を感じることができた気がします。
実はこの Tutorial 続きがあってもう少し機能追加したんですが、思ったより記事が膨らみそうなので書くのはやめておきます。
React あたりの技術が使えればロードのないヌメヌメ更新されるかっこいいもだんなやつが作りやすそうなので、次は何かしら作ってみます。