Reactでヌメロンゲームを作った話(stateの更新)
TypeScript
JavaScript
投稿日時:
2021-12-11
まずヌメロンゲームとは
答えの数字(今回は3桁)が用意されていて自分が入力した数字によってbite(数字と桁が一致),eat(数字が一致)が表示されてそのヒントをもとに完全一致を目指すゲーム。
state管理するもの
Reactでは状態が変化するものをstateで管理するので、まずそれを考える。
- 入力された値
- Bite数
- Eat数
- 結果(これはstateで管理する必要ないかも)
早速コード
import React, { useState } from "react";
import "./styles.css";
const answer: string[] = ["4", "5", "6"];
export default function App() {
const [num, setNum] = useState<string>("");
const [biteCount, setBiteCount] = useState(0);
const [eatCount, setEatCount] = useState(0);
const [result, setResult] = useState("");
const changeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
setNum(e.target.value);
};
const clickHandler = () => {
const numArray = num.split("");
setBiteCount(0);
setEatCount(0);
numArray.forEach((elem, index) => {
if (answer.includes(elem)) {
if (answer[index] === elem) {
setBiteCount((prev) => prev + 1);
} else {
setEatCount((prev) => prev + 1);
}
}
});
setResult(`${biteCount}BITE${eatCount}EAT`);
};
return (
<div className="App">
<input onChange={changeHandler} type="number" value={num} />
<button onClick={clickHandler}>判定</button>
{result && <p>{result}</p>}
</div>
);
}
今回は答えを定数[4,5,6]にしている。
入力された値をひとつづつ区切って配列にしてforEachで回してBite,Eat数をカウントしている。
実はこのコード欠陥がある。
クリックした際に一回目のクリックで前回のカウント数を表示してしまうのだ。
つまり2回クリックしないと正常に動作しないという事。
なぜそんなことが起こるかというとクリックした際にbiteCount,eatCountを更新する処理を行っているのだが、同じ関数内でsetResultを使ってbite,eatを更新しているので、実はstateの状態は更新される前のbite,eatつまり一つ前の状態になっている。
onblurを使う
onblurとはフォーカスが外れた時に発生するイベント。
つまりinputからフォーカスが外れた時にbite,eatを更新する処理を走らせればよい。
状態が更新されるタイミングは以下の通りになる。
- inputの変更を検知した時=>numの更新
- inputからフォーカスが外れた時=>bite,eatの更新
- 判定ボタンを押した時=>resultの更新
改善後のコード
import React, { useState } from "react";
import "./styles.css";
const answer: string[] = ["4", "5", "6"];
export default function App() {
const [num, setNum] = useState<string>("");
const [biteCount, setBiteCount] = useState(0);
const [eatCount, setEatCount] = useState(0);
const [result, setResult] = useState("");
const changeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
setNum(e.target.value);
};
const blurHandler = () => {
setBiteCount(0);
setEatCount(0);
const numArray = num.split("");
numArray.forEach( (elem, index) => {
if (answer.includes(elem)) {
if (answer[index] === elem) {
setBiteCount((prev) => prev + 1);
} else {
setEatCount((prev) => prev + 1);
}
}
});
};
const clickHandler = () => {
setResult(`${biteCount}BITE${eatCount}EAT`);
};
return (
<div className="App">
<input
onChange={changeHandler}
onBlur={blurHandler}
type="number"
value={num}
/>
<button onClick={clickHandler}>判定</button>
{result && <p>{result}</p>}
</div>
);
}
こんな感じになった。onblurは意外と良いかもしれん。
結論
結果をstateで管理しなければこんな面倒なことにはならん。