【Next.js】チケット販売システムを作る② : Array.from() を活用し、簡易Botチェックを実装

Programming

前回は チケット販売システムを作るを使って、正確なカウントダウン機能を実装しました。

今回は、実際のチケット販売サイトである「インターパーク」の販売フローを簡単に整理し、Botチェックの部分を簡易的に再現してみました。

ヒョニ
ヒョニ

インターパークのチケット販売は、販売開始時間になると「販売開始」ボタンが有効になり、クリックすると待機ページ(仮想待合室)へ移動します。順番が来ると座席選択へ進むことができ、その前にBotチェックが表示されます。認証が完了すると、座席や日付を選択し、決済まで進むことで予約が確定します。

本来のBotチェックでは、文字が画像として表示され、コピー&ペーストができない仕組みになっています。しかし今回は、あくまで「タイミング練習」が目的のため、構造理解を優先し、シンプルな形で実装しています。

captcha例

今回は簡易的なBot対策として、

  • ランダムな英大文字6文字を生成
  • ユーザー入力と一致するか判定
  • 一致すれば次の処理へ進む

という流れを実装しました。

この記事でわかること
  • Array.from() の使い方
  • Math.random() の使い方
  • ReactでのState管理
  • 簡単なバリデーション処理の流れ

CaptchaGateコンポーネント

CaptchaGateコンポーネントを生成し、Botチェックのために必要な機能をまとめました。

  1. A〜Z の文字列を用意
  2. Array.from() で長さ6の配列を作る
  3. Math.random() でランダムなインデックスを取得
  4. 6文字を結合して文字列にする
  5. state に保存
  6. ユーザー入力と比較
  7. 正しければ onSuccess() 実行

コード説明

generateCaptcha()関数を生成し、ランダム英字の生成ロジックを実装します。

generateCaptcha()の全体コード
const generateCaptcha = (): string => {
  const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

  return Array.from({ length: 6 }, () =>
    characters.charAt(
      Math.floor(Math.random() * characters.length)
    )
  ).join("");
};
入力欄
characters

A〜Zまでの文字を入れる変数を宣言

コードを見る
  const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
Array.from()
  • 長さ6の配列を作る
  • 第二引数に関数を書くと、各要素を生成できる
  • 最後に .join("") で文字列化
コードを見る
Array.from({ length: 6 }, () =>
    characters.charAt(
      Math.floor(Math.random() * characters.length)
    )
  ).join("");
Math.random()
  • 0以上1未満の小数を返す
  • そのままだと小数なので Math.floor() で整数化
コードを見る
//0 〜 25 の整数が作られます。
Math.floor(Math.random() * characters.length)
State変数宣言

それぞれを管理するState変数を宣言します。

  • code → ランダムに生成された正解コード
  • input → ユーザ入力
  • error → エラー状態の判定
コードを見る
const [code, setCode] = useState(() => generateCaptcha());
const [input, setInput] = useState("");
const [error, setError] = useState(false);
入力欄のポイント

小文字で入力しても大文字で変換してくれます。

コードを見る
onChange={(e) => {
  setInput(e.target.value.toUpperCase())
}}
handleSubmit():判定処理

入力した文字とランダム文字が一致するかを確認します。

一致しない場合は、もう一度Botチェックを行います。

error判定
コードを見る
useEffect(()=>{
        inputRef.current?.focus();
    },[])
   //判定処理
    function handleSubmit(){
        if(input == code){
            console.log("OK");
            onSuccess();
        }else{
            console.log("X");
            setError(true);
            setCode(generateCaptcha());
            setInput("");
        }
    }
全体コード

全体コードはこちらになります。

コードを見る
"use client"

import { useEffect, useRef, useState } from "react"


type Props = {
    onSuccess:()=>void;
}

export default function CaptchaGate({ onSuccess }: Props){

    const generateCaptcha = (): string => {
        const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        return Array.from({ length: 6 }, () => 
        characters.charAt(Math.floor(Math.random() * characters.length))
        ).join("");
    };

    // bot captcha code
    const [code, setCode] = useState(() => generateCaptcha());

    // user input
    const [input, setInput] = useState("");

    // validation error
    const [error, setError] = useState(false);

    // current input ref
    const inputRef = useRef<HTMLInputElement>(null);




    useEffect(()=>{
        inputRef.current?.focus();
    },[])

    function handleSubmit(){
        if(input == code){
            console.log("OK");
            onSuccess();
        }else{
            console.log("X");
            setError(true);
            setCode(generateCaptcha());
            setInput("");
        }
    }


    return(
        <div className="flex flex-col items-center justify-center text-center ">
            <div>
                {code}
            </div>
            <div>
            <input 
                ref={inputRef}
                value={input}
                maxLength={6}
                onChange={(e)=>{ setInput(e.target.value.toUpperCase())}}
                onKeyDown={(e)=> e.key === "Enter" && handleSubmit()}
                className="border-1 block outline-none text-center"
            >
            </input>
            <button 
                type="button" 
                onClick={()=>handleSubmit()}
                className="mt-4 pr-2"
            >
                Submit
            </button>
            </div>

        </div>
    )
}

Array.from()の使い方

Array.from() は配列のようなオブジェクトや反復可能な値から、新しい配列を作成するメソッドです。

今までは for文 で繰り返し処理を書いていましたが、Array.from() を使うことで、配列の生成と要素の作成を1行で記述できます。

String→配列
Array.from("おはよう")
//["お","は","よ","う"]
set→配列
const set = new Set([1, 2, 3]);

Array.from(set)
// [1, 2, 3]
map→配列
const map = new Map([
  ["a", 1],
  ["b", 2],
]);

Array.from(map)
// [["a", 1], ["b", 2]]

Array.from(map.values());
// ['1', '2'];

Array.from(map.keys());
// ['a', 'b'];
長さを指定して配列を作る(関数+インデックス)

length を指定することで、繰り返し回数を決められます。

Array.from({ length: 5 }, (_, i) => i);
// [0, 1, 2, 3, 4]
二次関数→配列
Array.from({ length: 5 }, (_, x) => x * x);
// [0, 1, 4, 9, 16]

console.log(Array.from([1, 2, 3], x => x * x)); 
// [1, 4, 9]
なぜ Array.from() が便利?

以前までよく使ったコードとArray .from()を使ったコードを比較しました。

for文
const result = [];
for (let x = 0; x < 5; x++) {
  result.push(x * x);
}
Array.from()
Array.from({ length: 5 }, (_, x) => x * x);

終わりに

今日もこちらの記事を読んでいただき、ありがとうございます!次回はいよいよ座席をGridレイアウトで実装し、実際に選択できる仕組みまで紹介します。

次回の記事もぜひ楽しみにしていてください!

※ 本記事のBotチェックは学習目的の簡易実装です。本番環境ではreCAPTCHAなどの専門的な認証サービスを利用する必要があります。

タイトルとURLをコピーしました