🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

Q&A

解決済

1回答

1275閲覧

Reactでの画面描画のタイミングについて

Kentarou3

総合スコア1

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

0グッド

0クリップ

投稿2021/01/10 03:49

編集2021/01/10 23:37

Reactでタイピンングゲームを作成する際の画面描画のタイミングについて

Reactでタイピングゲーム を作成しているのですが、画面描画のタイミングがうまく行かずに困っています。
下のhtmのコードをブラウザ上で開いてもらうと、

abc
a__

という画面が出てきます。bcとタイプすると、abcと発音するようになっているのですが、cが描画される前にabcと発音し、
終わったらcが描画されるような流れになっています。これを、cが描画された後にabcと発音するように修正したいです。

該当のソースコード

React

1<!DOCTYPE html> 2<html> 3<head> 4 <meta charset="UTF-8" /> 5 <title>React</title> 6 <script src="https://unpkg.com/react@17/umd/react.development.js"></script> 7 <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> 8 <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> 9</head> 10 11<body> 12 <div id="root"></div> 13 <script type="text/babel"> 14 15 (() => { 16 17 class App extends React.Component { 18 constructor() { 19 super(); 20 this.state = { 21 answer: "abc", 22 blank: "a__", 23 location: 1, 24 speak: false, 25 } 26 this.keydown = this.keydown.bind(this) 27 this.speak = this.speak.bind(this) 28 } 29 speak(word) { 30 var speak = new SpeechSynthesisUtterance(); 31 speak.text = word 32 speak.rate = 1.0 33 speak.pitch = 0 34 speak.lang = 'en-US' 35 speechSynthesis.speak(speak) 36 37 let i = 0 38 while (i < 1000000000) 39 i++ 40 } 41 keydown(e) { 42 console.log(e.key) 43 if (this.state.answer[this.state.location] == e.key) { 44 this.setState((state, props) => ({ 45 location: state.location + 1 46 })); 47 let blank = this.state.answer.substring(0, this.state.location) + '_'.repeat(this.state.blank.length - this.state.location); 48 49 this.setState({ 50 blank: blank 51 }) 52 console.log(blank) 53 54 if (this.state.answer.length == this.state.location) { 55 this.setState({ 56 speak: true 57 }) 58 } 59 } 60 } 61 62 componentDidMount() { 63 window.addEventListener('keydown', this.keydown) 64 } 65 66 componentDidUpdate() { 67 if (this.state.speak == true) { 68 this.speak(this.state.answer) 69 } 70 } 71 72 render() { 73 return ( 74 <div> 75 <div> 76 {this.state.answer} 77 </div> 78 <div> 79 {this.state.blank} 80 </div> 81 </div> 82 ); 83 } 84 } 85 86 ReactDOM.render( 87 <App />, 88 document.getElementById('root') 89 ); 90 })(); 91 92 </script> 93</body> 94 95</html>

2021/1/11修正版

React

1 2<!DOCTYPE html> 3<html> 4 5<head> 6 <meta charset="UTF-8" /> 7 <title>React</title> 8 <script src="https://unpkg.com/react@17/umd/react.development.js"></script> 9 <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> 10 <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> 11</head> 12 13<body> 14 <div id="root"></div> 15 <script type="text/babel"> 16 17 (() => { 18 19 class App extends React.Component { 20 constructor() { 21 super(); 22 this.state = { 23 answer: "abc", 24 blank: "a__", 25 location: 1, 26 speaking: undefined, 27 } 28 this.keydown = this.keydown.bind(this) 29 this.speak = this.speak.bind(this) 30 } 31 speak(word) { 32 var speech = new SpeechSynthesisUtterance(word) 33 speechSynthesis.speak(speech) 34 console.log(speechSynthesis) 35 this.setState({ 36 ...this.state, 37 speechSynthesis 38 }) 39 speech.onend = function (event) { 40 console.log(event) 41 console.log(speechSynthesis) 42 } 43 } 44 keydown(e) { 45 console.log(e.key) 46 47 if (this.state.answer[this.state.location] == e.key) { 48 this.setState((state, props) => ({ 49 location: state.location + 1 50 })); 51 let blank = this.state.answer.substring(0, this.state.location) + '_'.repeat(this.state.blank.length - this.state.location); 52 53 this.setState({ 54 blank: blank 55 }) 56 console.log(blank) 57 58 if (this.state.answer.length == this.state.location) { 59 this.speak(this.state.answer) 60 console.log(this.state.speechSynthesis) 61 // while (this.state.speechSynthesis.speaking) 62 this.setState({ 63 ...this.state, 64 answer: "def", 65 blank: "d__", 66 location: 1, 67 }) 68 } 69 } 70 } 71 72 componentDidMount() { 73 window.addEventListener('keydown', this.keydown) 74 } 75 76 render() { 77 return ( 78 <div> 79 <div> 80 {this.state.answer} 81 </div> 82 <div> 83 {this.state.blank} 84 </div> 85 </div> 86 ); 87 } 88 } 89 90 ReactDOM.render( 91 <App />, 92 document.getElementById('root') 93 ); 94 })(); 95 96 </script> 97</body> 98 99</html>

試したこと

componentDidUpdateとフラグの処理などを使って試してみましたが、どのようにしても思うような結果になりません。。
Reactは最後にrender関数で描画していると思うので、そのようになって当然な気もするのですが、何かいい方法はないでしょうか。
render関数が実行された後に実行されるライフルサイクルメソッドのようなものがあればと思ったのですが。。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

謎の while を取って

JavaScript

1 speak(word) { 2 var speak = new SpeechSynthesisUtterance(); 3 speak.text = word 4 speak.rate = 1.0 5 speak.pitch = 0 6 speak.lang = 'en-US' 7 speechSynthesis.speak(speak) 8 }

JavaScript

1 if (this.state.answer.length == this.state.location) { 2 this.speak(this.state.answer) 3 }

ではどうでしょうか。

追記:
音声が終わる前に次の回答がセットされるのを防ぐには SpeechSynthesis.speaking を使うのが良さそうです。
SpeechSynthesis.speaking - Web APIs | MDN
まず

JavaScript

1speech = speechSynthesis.speak(utterance) // speak だと名前が紛らわしいので変えた 2this.setState({...this.state,speech}) // スプレッド演算子でコピーを取らないと意図しない挙動をすることがあるので注意

として適当に状態にオブジェクトを保存しておきます。次に正解したときの処理を

JavaScript

1if (this.state.answer.length == this.state.location) { 2 this.speak(this.state.answer) 3 while(this.state.speech.speaking); 4}

とすれば speakingfalse になるまで待つことができるでしょう。

あと var は使わないで letconst 使うようにして……。

追記 2:
すみません、ドキュメントをよく読んだら onend イベントがありました……。なのでそれを使えば大丈夫です。
SpeechSynthesisUtterance - Web API | MDN
アロー関数にしてるのは function だと this が参照先で変わってしまうから。

HTML

1<!DOCTYPE html> 2<html> 3 4<head> 5 <meta charset="UTF-8" /> 6 <title>React</title> 7 <script src="https://unpkg.com/react@17/umd/react.development.js"></script> 8 <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> 9 <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> 10</head> 11 12<body> 13 <div id="root"></div> 14 <script type="text/babel"> 15 16 (() => { 17 18 class App extends React.Component { 19 constructor() { 20 super(); 21 this.state = { 22 answer: "abc", 23 blank: "a__", 24 location: 1, 25 speaking: undefined, 26 } 27 this.keydown = this.keydown.bind(this) 28 this.speak = this.speak.bind(this) 29 } 30 speak(word) { 31 var speech = new SpeechSynthesisUtterance(word) 32 speech.onend = () => { 33 this.setState({ 34 ...this.state, 35 answer: "def", 36 blank: "d__", 37 location: 1, 38 }); 39 } 40 speechSynthesis.speak(speech) 41 } 42 keydown(e) { 43 console.log(e.key) 44 45 if (this.state.answer[this.state.location] == e.key) { 46 this.setState((state, props) => ({ 47 location: state.location + 1 48 })); 49 let blank = this.state.answer.substring(0, this.state.location) + '_'.repeat(this.state.blank.length - this.state.location); 50 51 this.setState({ 52 blank: blank 53 }) 54 console.log(blank) 55 56 if (this.state.answer.length == this.state.location) { 57 this.speak(this.state.answer) 58 } 59 } 60 } 61 62 componentDidMount() { 63 window.addEventListener('keydown', this.keydown) 64 } 65 66 render() { 67 return ( 68 <div> 69 <div> 70 {this.state.answer} 71 </div> 72 <div> 73 {this.state.blank} 74 </div> 75 </div> 76 ); 77 } 78 } 79 80 ReactDOM.render( 81 <App />, 82 document.getElementById('root') 83 ); 84 })(); 85 86 </script> 87</body> 88 89</html>

投稿2021/01/10 04:10

編集2021/01/11 07:42
A_kirisaki

総合スコア2853

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Kentarou3

2021/01/10 04:26

ご回答ありがとうございます。 このコードではwhileの意図がわからず申し訳ありません。 whileを入れている理由は、次の問題に行くまでにabcを発音する十分な時間を確保するためです。 次の問題をセットするタイミングがいつが適切なのかがわからないのですが、例えば以下のようにした場合、 次のdefが表示されている時にabcが発音されることになってしまいます。 ```JavaScript if (this.state.answer.length == this.state.location) { this.speak(this.state.answer) this.setState({ answer: "def", blank: "d__", location: 1, }) } ``` ---
Kentarou3

2021/01/10 23:14 編集

すみません、うまく行きません。。 オブジェクトのセットのところで失敗しているみたいなのと、別途色々試していたのですが、 speech.speakingの結果がtrueにしかなりません。。 関連しそうな箇所のソースコードを貼り付けたので、まずい処理をしている箇所を教えていただけるとありがたいです。 ``` speak(word) { var utterance = new SpeechSynthesisUtterance(word) var speech = speechSynthesis.speak(utterance) this.setState({ ...this.state, speech }) console.log(speech) } keydown(e) { console.log(e.key) if (this.state.answer[this.state.location] == e.key) { this.setState((state, props) => ({ location: state.location + 1 })); let blank = this.state.answer.substring(0, this.state.location) + '_'.repeat(this.state.blank.length - this.state.location); this.setState({ blank: blank }) console.log(blank) if (this.state.answer.length == this.state.location) { this.speak(this.state.answer) while (this.state.speech.speaking) this.setState({ answer: "def", blank: "d__", location: 1, }) } } ```
A_kirisaki

2021/01/10 16:23

すみません、 var utterance = new SpeechSynthesisUtterance(word) var speech = speechSynthesis.speak(utterance) が var speech = new SpeechSynthesisUtterance(word) speechSynthesis.speak(speech) のようでした!
Kentarou3

2021/01/10 23:47

夜遅くにご回答ありがとうございます。該当ソースコードのところに、ご指摘いただいた点を修正したコードを載せておきました。 音声を出すところと、speechSynthesisオブジェクトをsetStateするところまではできたのですが、登録したsppehSynthesisオブジェクトのspeakingをwhileで実行するとfalseに変化せず無限ループになってしまいます。。 speech.onend内で確認するとfalseにはなるのは確認したのですが、このようにコピーしたオブジェクトの状態も変化するものなのでしょうか? また、無限ループ中でもabの表示のまま無限ループになってしまうので、当初の目的だった、abcと表示させて音声が終わるのを待って次の問題に行くというのが、このやり方では達成できないような気がします。。 何かアドバイス等頂けるとありがたいです。よろしくお願いいたします。
A_kirisaki

2021/01/11 04:08

this.setState({ ...this.state, speechSynthesis }) どうしてそうなった……。
Kentarou3

2021/01/11 06:06

speechSynthesis.speakingがtrue/falseを返すので、speechSynthesisをセットしましたが、何かおかしいでしょうか。。。
A_kirisaki

2021/01/11 06:10

speech = speechSynthesis.speak(utterance) でオブジェクトを作成しており、このオブジェクトを通して状態を検知できるのです。なので speech を共有しないといけないので this.setState({ ...this.state, speech }) になります。
Kentarou3

2021/01/11 06:39 編集

すみません。。パニックです。。 以下の3行を書いて実行しても、speechはundefinedになってしまいます。。 var utterance = new SpeechSynthesisUtterance(word) var speech = speechSynthesis.speak(utterance) console.log(speech) //undefined
Kentarou3

2021/01/11 07:01

すみませんが、意図する動きになってなさそうです。。。 ここで渡しているspeechですが、SpeechSynthesisUtteranceオブジェクトで 中身は下の方に載せました。渡さないといけないのは、speechSynthesisオブジェクトだと思うのですが。。。 var speech = new SpeechSynthesisUtterance(word) speechSynthesis.speak(speech) console.log("speech obj = ", speech) this.setState({ ...this.state, speech }) SpeechSynthesisUtterance {text: "abc", lang: "", voice: null, volume: -1, rate: -1, …} lang: "" onboundary: null onend: ƒ (event) onerror: null onmark: null onpause: null onresume: null onstart: null pitch: -1 rate: -1 text: "abc" voice: null volume: -1 __proto__: SpeechSynthesisUtterance
Kentarou3

2021/01/11 07:56

動きました! onendがあることはわかっていたのですが、setStateが出来ずに困っていました。。 thisが参照先で変わってしまう。。。勉強不足ですね。勉強します。 ありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問