前回作成したものを基に今回は入力画面の挙動を変更していきたいと思います。
DEMO
何かの機能を実装しようと思ったときに、大きく分けて2つの側面があります。
一つは要素の色や表示件数をかえたり目に見える形の部分。
もう一つは、どのような条件で色や表示件数を可変させるか。
多くの場合目で確認できる表示側を優先した方が早く簡単に実装できます。
今回はモックの値を入れつつ、入力画面に表示機能を追加していきたいと思います。
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"
style={{ backgroundColor: colors[props.status] }}
onClick={() => console.log("ボタンを押したよ", props.number)}
>
{props.number}
</button>
);
const StarMatch = () => {
const [stars, setStars] = useState(utils.random(1, 9));
const [availableNums, setAvailableNums] = useState([1, 2, 3, 4, 5]);
const [candidateNums, setCanditateNums] = useState([2, 3]);
const candidatAreWrong = utils.sum(candidateNums) > stars;
const numberStatus = (number) => {
if (!availableNums.includes(number)) {
return "used";
}
if (candidateNums.includes(number)) {
return candidatAreWrong ? "wrong" : "candidate";
}
return "available";
};
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}
status={numberStatus(number)}
/>
))}
</div>
</div>
</div>
);
};
// Color Theme
const colors = {
available: "lightgray",
used: "lightgreen",
wrong: "lightcoral",
candidate: "deepskyblue"
};
// Math science
const utils = {
// Sum an array
sum: (arr) => arr.reduce((acc, curr) => acc + curr, 0),
// 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);
### 選択候補の数字
### 選択できない数字
### 既に使用されている数字
### 使用可能な数字
Reactのドキュメントにも書いてあるのですが、useStateなどで指定している状態の数をなるべく少なくすることが推奨されています。
4うちどの機能を状態しておくかというと
### 選択候補の数字
### 使用可能な数字
となります。
理由としては
選択できない数字 => 対象の候補(星)の数がわかった状態であれば使用可能な数字と比較すれば計算できるためです。
既に使用されて数字 => 使用可能な数字の対偶となるわけなので、どちらかの値を持っていれば良いということになります。
const [stars, setStars] = useState(utils.random(1, 9));
// 使用可能な数字 A
const [availableNums, setAvailableNums] = useState([1, 2, 3, 4, 5]);
// 選択候補の数字 B
const [candidateNums, setCanditateNums] = useState([2, 3]);
// 選択できない数字 C
const candidatAreWrong = utils.sum(candidateNums) > stars;
const numberStatus = (number) => {
if (!availableNums.includes(number)) {
return "used";
}
if (candidateNums.includes(number)) {
return candidatAreWrong ? "wrong" : "candidate";
}
return "available";
};
status={numberStatus(number)}
これはstatus自体を直接渡して、それぞれの数値コンポーネンをの値を渡すためにそうしています。
numberStatus()というstatusを管理する関数を導入し、ここへID(number)を渡して、starMatchでステータスデータを渡してあげるようにしてあげます。
const numberStatus = (number) => {
if (!availableNums.includes(number)) {
return "used";
}
if (candidateNums.includes(number)) {
return candidatAreWrong ? "wrong" : "candidate";
}
return "available";
};
includes() メソッドは、特定の要素が配列に含まれているかどうかをbooleanで返すものです。
availableNumsの中にPlayNumberの中にある1~9の数字がなかったら"used" をreturnするという式です。
(今回availableNumsはuseState([1, 2, 3, 4, 5])としていしているので、6,7,8,9が対象になります)
二つ目のif分岐はcandidateNumsが[2, 3]であれば、
星の数より大きい場合は"wrong”でそうでない場合は"candidate”を返すような式です。
// Color Theme
const colors = {
available: "lightgray",
used: "lightgreen",
wrong: "lightcoral",
candidate: "deepskyblue"
};
先ほどnumberStatus()内でreturnしていたものが何かというと、
Ifで分岐した返り値に応じて色を変更するためのkeyになります。
const PlayNumber = (props) => (
<button
className="number"
style={{ backgroundColor: colors[props.status] }}
onClick={() => console.log("ボタンを押したよ", props.number)}
>
{props.number}
</button>
);
それらの返り値はPlayNumberの中へstyleのbackgroundColorとして配置しています。
これらが正しく実装されていれば、ステータスに応じて入力欄の色が変わるようになるかと思います!!
今日のところは以上で終わりたいと思います!
最後までありがとうございました!