ブログの投稿ログ

2021年
9月
8月
7月
6月

初めてのREACT-v13

Statusに応じて要素を変更したい!

前回作成したものを基に今回は入力画面の挙動を変更していきたいと思います。
DEMO

何かの機能を実装しようと思ったときに、大きく分けて2つの側面があります。
一つは要素の色や表示件数をかえたり目に見える形の部分。
もう一つは、どのような条件で色や表示件数を可変させるか。

多くの場合目で確認できる表示側を優先した方が早く簡単に実装できます。

今回はモックの値を入れつつ、入力画面に表示機能を追加していきたいと思います。

今回のコード

codesandbox

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);

状態の洗い出し


### 選択候補の数字
### 選択できない数字
### 既に使用されている数字
### 使用可能な数字

の4つが今回のステータスとなります。

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";
  };


まずAとBは状態として実装するため、初期値を入れていきます。 今回はモックを使用して、UIに集中できるようにしましょう。
今回は2と3を候補として表示するように指定しました。 星の数が5個未満の場合それらは選択できない数字となり、 5個以上の場合は青く表示するよう実装します。
また、6,7,8,9は使用済みの数字として緑に表示していきたいと思います。
したがってAとBの数値コンポーネントをレンダリングできるように星の数に紐付ける必要があります。
そのためにPlayNumberタグへStatusを渡しているのが確認できるかと思います。
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として配置しています。 これらが正しく実装されていれば、ステータスに応じて入力欄の色が変わるようになるかと思います!!

今日のところは以上で終わりたいと思います!

最後までありがとうございました!