前回に引き続き足し算ゲームを作っていきたいと思います。
DEMO
10秒以内に黒い星の数と同じ数だけ右の数字から選んでいく物となります。
今回はそれぞれの機能を持った要素を関数コンポーネントに分けて表示したと思っています。
import React, { useState } from "react";
import { render } from "react-dom";
import "./styles.css";
const StarsDisplay = (props) => (
<>
{utils.range(1, props.count).map((starId) => (
<div key={starId} className="star" />
))}
</>
);
const PlayNumber = (props) => (
<button
className="number"
onClick={() => console.log("ボタンを押したよ", props.number)}
>
{props.number}
</button>
);
const StarMatch = () => {
const [stars, setStars] = useState(utils.random(1, 9));
return (
<div className="game">
<div className="body">
<div className="left">
<StarsDisplay count={stars} />
</div>
<div className="right">
{utils.range(1, 9).map((number) => (
<PlayNumber key={number} number={number} />
))}
</div>
</div>
</div>
);
};
// Math science
const utils = {
// create an array of numbers between min and max (edges included)
range: (min, max) => Array.from({ length: max - min + 1 }, (_, i) => min + i),
// pick a random number between min and max (edges included)
random: (min, max) => min + Math.floor(Math.random() * (max - min + 1))
};
const rootElement = document.getElementById("root");
render(<StarMatch />, rootElement);
前回はStarMatchという名前のコンポーネント1つで全ての機能を内包していましたが、それだと非常に大きなものとなってしまいます。
そこでそれぞれの機能に分けて責任の分担をして上げる必要があるのです。
と言ってもどこを独立させてコンポーネントに切り分けるのかという問題があります。
適切なコンポーネントとしてポイントとなるのが、
で判断することができると思います。
今回でいうところの”星を出力する要素”と”番号を入力する要素”がそれにあたるものとなるので、早速それぞれを関数コンポーネント化していきましょう!
const StarMatch = () => {
return (
<div className="right">
{utils.range(1, 9).map((number) => (
<button key={number} className="number">
{number}
</button>
))}
</div>
);
};
const PlayNumber = (props) => (
<button
className="number"
onClick={() => console.log("ボタンを押したよ", props.number)}
>
{props.number}
</button>
);
const StarMatch = () => {
<div className="right">
{utils.range(1, 9).map((number) => (
<PlayNumber key={number} number={number} />
))}
</div>
}
入力側の挙動としてはボタンを入力するだけであって、動的に要素(タグ)を消したり加えたりする機能ではないことがわかるでしょうか?
なので、button要素のみコピーしてそれを関数コンポーネントへ内包して出力する流れとなります。
Beforeのbottun要素をそのままコピーすると当然ですが動きません。
出力したい箇所に
Map内の仮引数numberをnumber={number}として1~9の値を一つずつ{number}プロパティへ入れてあげます。
それからkeyはループする対象の要素に必要になってくるので、key={number}として置いておきます。
次にテストで
onClick={() => console.log("ボタンを押したよ", props.number)}
として、console.logを吐いてみました。
すると、数字のボタンを押すとその数字がlogとして出力されているのが確認できるかと思います。
これはmap内で1~9の値を吐き出しているのですが、その数だけonClickがついて、numberプロパティが付与されていることになります。
Reactではこのような便利な実装が可能となっていますが、これらにはJavascriptクロージャの知見が必須となるので勉強しておきましょう!!
const StarMatch = () => {
return (
<div className="left">
{utils.range(1, satrs).map((starId) => (
<div key={starId} className="star" />
))}
</div>
);
};
const StarsDisplay = (props) => (
<>
{utils.range(1, props.count).map((starId) => (
<div key={starId} className="star" />
))}
</>
);
const StarMatch = () => {
<div className="left">
<StarsDisplay count={stars} />
</div>
}
星側の実装はレンダリングされる毎に要素の数を変更するような挙動になります。
なので、ごっそりと要素を出力する関数ごとコンポーネント内に移してしまいましょう。
"<></>"が何なのかというと、Reactの使用上関数コンポーネント内に要素を書き込むときはdivタグで囲わなければならないというルールがあります。
それを回避するためにreact.fragmentというものを使うのですが、それを省略した形が"<></>"になるわけですね!
そしてコンポーネント化する事でもう一つ問題なのは星の数を受け取れないことにあります。
const StarMatch = () => {
const [stars, setStars] = useState(utils.random(1, 9));
}
見ての通り、星の数を受け取っているのはStarMatch関数ないとなるので、StarsDisplay関数ではそのまま使えません。
そこで、
そうすることで props.countをStarsDisplay関数で実行して上げると値が取れるわけですね!
今回は3つの関数コンポーネントへ振り分ける作業を行いました。
備考になってしまうのですが、入力側の関数コンポーネント名を直感的なNumberとしなかった理由を説明しようと思います。
Javascriptで既存のクラスにあるNumber関数があるのをご存知ですよね?
例えば文字列で入力されたものをNumber型にする必要があるケースを想定できるかと思います。
const [stars, setStars] = useState(utils.random(Number(“1”), 9));
仮にこうしたときにNumberは既に定義してしまっているので、文字列をNumber型へ戻すことが不可能となってしまうのです。
以上で今回のお勉強を終わります。