現在、Reactにて簡単なタイピングゲームを作成しているのですが、遅延実行/setTimeoutの機能を実装しようとしたところエラーが発生し、詰まってしまいました。
お力をお借りできますと幸いです。
前提・実現したいこと
単語の最後の文字を入力して、0.4秒後に次の単語に遷移するようにしたい。
本ゲームでは、未入力の文字は灰色、入力済みの文字は黒色になるよう、cssを書き換えています。
しかし、現状では最後の文字入力が完了した瞬間に、次の単語に遷移してしまうため、最後の文字が黒色(入力完了)となった状態が表示されません。
そこで、遅延実行を導入し、最後の文字を入力して文字が黒色になったあと、0.4秒後に次の単語に遷移するようにしたいと思っています。
発生している問題・エラーメッセージ
下記エラーが発生してしまいます。
TypeError Cannot set properties of undefined (setting 'className') eval /src/App.tsx:84:12 81 | wordTransition(number + 1); 82 | textSpans[0].className = "current-letter"; 83 | for (let i = 1; i < text.length; i++) { > 84 | textSpans[i].className = " waiting-letters"; | ^ 85 | } 86 | }, 400); 87 | }
なお、単語によっては発生しない場合もあります。
現在、「inu」→「kitsune」という順番で、単語が遷移していくのですが、「inu」のときはエラーが出ず「kitsune」の時にはエラーが出てしまいます。
該当のソースコード
▼Codesandbox
https://codesandbox.io/s/musing-wind-x404d?file=/src/App.tsx
React
1import React, { useState, useEffect, useRef } from "react"; 2import "./App.scss"; 3 4export default function App() { 5 //単語データ 6 const wordList = [ 7 "inu", 8 "kitsune", 9 "ookami", 10 "baison", 11 "kitsune", 12 "usagi", 13 "inu", 14 "neko", 15 "ningen", 16 "hitsuji" 17 ]; 18 19 //ルビ 20 const rubiList = [ 21 "いぬ", 22 "きつね", 23 "おおかみ", 24 "ばいそん", 25 "きつね", 26 "うさぎ", 27 "いぬ", 28 "ねこ", 29 "にんげん", 30 "ひつじ" 31 ]; 32 33 //漢字 34 const answerList = [ 35 "犬", 36 "狐", 37 "狼", 38 "バイソン", 39 "狐", 40 "ウサギ", 41 "犬", 42 "猫", 43 "人間", 44 "羊" 45 ]; 46 47 //タイピングに関する部分 48 const [number, setNumber] = useState(0); 49 const [position, setPosition] = useState(0); 50 51 const text = wordList[number]; 52 const answer = answerList[number]; 53 const rubi = rubiList[number]; 54 55 // 単語の変更 56 const wordTransition = (nextNum: number) => { 57 setNumber(nextNum); 58 setPosition(0); 59 60 console.log(nextNum); 61 }; 62 63 const handleKey = (e: React.KeyboardEvent<HTMLDivElement>) => { 64 // 文字の配列を取得 65 let textSpans = document.querySelector("#textbox").children; 66 67 // 入力したキーと現在入力しようとしている文字が一致するとき 68 if (e.key === text[position]) { 69 // 現在の文字を入力済とする 70 textSpans[position].className = "typed-letters"; 71 textSpans[position].classList.remove("current-letter"); 72 73 // まだ入力していない文字があるとき 74 if (position <= text.length - 2) { 75 // 次の位置へ移動 76 textSpans[position + 1].className = "current-letter"; 77 setPosition(position + 1); 78 79 // 全ての文字を入力し終わったとき 80 } else { 81 // 今回追加した実行遅延 82 setTimeout( function() { 83 wordTransition(number + 1); 84 textSpans[0].className = "current-letter"; 85 for (let i = 1; i < text.length; i++) { 86 textSpans[i].className = " waiting-letters"; 87 } 88 }, 400 ); 89 } 90 91 // 間違ったキーを入力したとき 92 } else { 93 textSpans[position].classList.add("typo"); 94 } 95 }; 96 97 //入力エリアにフォーカスを当てる 98 const searchInput = useRef(null); 99 useEffect(() => { 100 searchInput.current.focus(); 101 }); 102 103 //戻るボタン 104 const backButton = () => { 105 wordTransition(number - 1); 106 let textSpans = document.querySelector("#textbox").children; 107 textSpans[position].className = " waiting-letters"; 108 }; 109 110 return ( 111 <> 112 <div 113 ref={searchInput} 114 className="App" 115 onKeyPress={(e) => handleKey(e)} 116 tabIndex={0} 117 > 118 <h1> 119 {/* 問題(漢字) */} 120 <ruby> 121 {answer} 122 <rt>{rubi}</rt> 123 </ruby> 124 </h1> 125 {/* 入力部分 */} 126 <div id="textbox"> 127 <span className="current-letter">{text[0]}</span> 128 {text 129 .split("") 130 .slice(1) 131 .map((char, index) => ( 132 <span className="waiting-letters" key={index}> 133 {char} 134 </span> 135 ))} 136 </div> 137 138 {/* 戻るボタン */} 139 <button 140 onClick={() => { 141 backButton(); 142 }} 143 > 144 戻る 145 </button> 146 <p>{number}/10</p> 147 </div> 148 </> 149 ); 150} 151
試したこと
海外の記事を参考に、下記のようにsetTimeoutをアロー関数にしたり、bind(this)を使用したのですが、うまくいきませんでした。
setTimeout(() => {}, 400); setTimeout(function() {}.bind(this), 400);
単語によって発生しない場合があることとエラー文から、ページのレンダリングのタイミングと今回の遅延実行による処理でタイミングの齟齬(「inu」の文字数に対しての命令と「kitsune」の文字数に対しての命令が混じっているなど)が起きているのかなと考えています。
しかし、JavascriptやReactの知識が乏しく打ち手が分からないため、何かお分かりの方がいらっしゃいましたら、お力をお借りできますと幸いです。
何卒宜しくお願い致します。
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/12/27 15:29