反応速度でネットゲームを実施


授業内容に基づいて、反応速度検出ネットワークゲームサイトを作成しました.
住所:http://3.35.201.212/
フルコード:https://github.com/Taewoong1378/ResponseCheck_Site_ErrorFix

1.反応速度検出原理


(1)使用状態:state、message、result
(2) state : waiting, ready, now
(3)画面構成は以下のとおりである.
  • 第一画面:クリック開始
  • 第2画面:緑クリック
  • の3番目の画面:(2番目の画面で速く押した場合)緑に変わり、をクリックします.
  • 第4画面:現在クリック(+左下隅表示平均時間とリセットボタンをクリック)
  • 2.ステータスが空の配列の場合に発生するエラー

    class ResponseCheck extends Components {
    	state = {
    		state: 'waiting',
    		message:'클릭해서 시작하세요',
    		result: [],	
    	};
    	
    	onClickScreen = () => {
    		...
    	};
    
    	render() {
    		return (
    			<>
    				...
    				***<div>평균 시간 : 
    				{this.state.result.reduce((a, c) => a +c) 
    			 / this.state.result.length} ms
    				</div>***
    			</>
    		);
    	}
    
    }
    前述したようにコードを記述すると、次のエラーが発生します.

    this.state.resultは最初は空の配列なので、加算関数reduceを使用するとエラーが発生します.この問題を解決する方法は、3つの条件演算子条件文を使用することです.reactのreturn文ではif文またはfor文を使用する方法がありますが、非常に複雑なため、通常3つの条件演算子が使用されます.
    class ResponseCheck extends Components {
    	state = {
    		state: 'waiting',
    		message:'클릭해서 시작하세요',
    		result: [],	
    	};
    	
    	onClickScreen = () => {
    		...
    	};
    
    	render() {
    		return (
    			<>
    				...
    				***result.length === 0 
            ? null 
            : <> 
            <div>
    					평균 시간: {result.reduce((a,c)=> a + c) / result.length}ms
    				</div>***
    			</>
    		);
    	}
    
    }
    result配列が空でlengthが0の場合、null値、すなわちresultは返されません.length配列が値のみの場合に<div>タグを返すと、上記のエラーは解決されます.

    3.クラスでuseref()を実装する方法


    ここでuseref()とは、DOMを操作するためのrefではなく、値を格納するためのrefを指す.以下の場合に使用します.以下の場合に使用します.
    class ResponseCheckClass extends Component {
        state = {
            state: 'waiting',
            message: '클릭해서 시작하세요.',
            result: [],
        };
    
        timeout;
        startTime;
        endTime;
    
        onClickScreen = () => {
            const { state, message, result} = this.state;
            if (state === 'waiting') {
                this.setState({
                    state: 'ready',
                    message: '초록색이 되면 클릭하세요.',
                });
                ***this.timeout = setTimeout(()=>{
                    this.setState({
                        state: 'now',
                        message: '지금 클릭',
                    });
                    this.startTime = new Date();
                }, Math.floor(Math.random() * 1000) + 2000); // 2~3초 랜덤***
            } else if (state === 'ready') { // 성급하게 클릭
                ***clearTimeout(this.timeout);***
                this.setState({
                    state: 'waiting',
                    message: '너무 성급하시군요! 초록색이 된 후에 클릭하세요.',
                })
            } else if (state === 'now') {   // 반응속도 체크
                this.endTime = new Date();
                this.setState((prevState) => {
                    return {
                        state: 'waiting',
                        message: '클릭해서 시작하세요.',
                        result: [...prevState.result, this.endTime -  this.startTime],
                    };
                });
            }
        };
    処理されたコードを見るとsettimeoutが実行されています.しかしsettimeoutを初期化しないと、「急いでクリックする」という文が出てきたら、再び「待機」状態に戻るのではなく、settimeoutによって「now」状態になり、「今クリックする」という文が出てきます.したがって、このsettimeoutを初期化する必要がありますが、クラスでtimeoutを宣言した後、this.timeoutにsettimoutを割り当て、clearTimeout(this.timeout)によってsettimoutを初期化することができる.

    4.Hooksでuseref()を使用する新しい方法


    stateが変更されると、下部の戻り部分は再レンダリングされますが、userefを使用する値は戻り部分をレンダリングするときに再レンダリングされません.したがって、再レンダリングしたくない値はrefで使用できます.値を変更したいが、画面に影響を与えたくない場合はrefを使用します.以下のように使用します.
    import React, { useState, useRef } from 'react';
    
    const ResponseCheckHooks = () => {
      const [state, setState] = useState('waiting');
      const [message, setMessage] = useState('클릭해서 시작하세요.');
      const [result, setResult] = useState([]);
    
      ***const timeout = useRef(null);  // 초깃값을 null로 설정한 것, 비워둬도 된다.
      const startTime = useRef();
      const endTime = useRef();***
    
      const onClickScreen = () => {
        if (state === 'waiting') {
          timeout.current = setTimeout(() => {
            setState('now');
            setMessage('지금 클릭');
            startTime.current = new Date();
          }, Math.floor(Math.random() * 1000) + 2000); // 2초~3초 랜덤
          // ref에 대입하는걸로는 실행이 안된다 (랜더링이 되지 않기 때문)
          // 변하는 값을 기록하는 역할을 하는 것이고, 밑에 setState를 만나면 리랜더링이 된다.
          setState('ready');
          setMessage('초록색이 되면 클릭하세요.');
        } else if () { 
    			
    			...
    
    大事なのは.
    refの代入は不可能です.setStateが存在する場合でも、refは変化した値を記録する役割を果たし、下部でsetStateに遭遇すると、その時点まで再ロードされ、refが反映されます.

    5.反応速度検査コードの実施


    以上を総合して,反応速度のチェックを実現したネットゲームのコードは以下の通りである.
    ResponseCheck.Hooksのjsxを使う
    import React, { useState, useRef } from 'react';
    
    const ResponseCheck = () => {
      const [state, setState] = useState('waiting');
      const [message, setMessage] = useState('클릭해서 시작하세요.');
      const [result, setResult] = useState([]);
    
      const timeout = useRef(null);
      const startTime = useRef();
      const endTime = useRef();
    
      const onClickScreen = () => {
        if (state === 'waiting') {
          timeout.current = setTimeout(() => {
            setState('now');
            setMessage('지금 클릭');
            startTime.current = new Date();
          }, Math.floor(Math.random() * 1000) + 2000); // 2초~3초 랜덤
          setState('ready');
          setMessage('초록색이 되면 클릭하세요.');
        } else if (state === 'ready') { // 성급하게 클릭
          clearTimeout(timeout.current);
          setState('waiting');
          setMessage('너무 성급하시군요! 초록색이 된 후에 클릭하세요.');
        } else if (state === 'now') { // 반응속도 체크
          endTime.current = new Date();
          setState('waiting');
          setMessage('클릭해서 시작하세요.');
          setResult((prevResult) => {
            return [...prevResult, endTime.current - startTime.current];
          });
        }
      };
      const onReset = () => {
        setResult([]);
      };
    
      const renderAverage = () => {
        return result.length === 0
          ? null
          : <>
            <div>평균 시간: {result.reduce((a, c) => a + c) / result.length}ms</div>
            <button onClick={onReset}>리셋</button>
          </>
      };
    
      return (
        <>
          <div
            id="screen"
            className={state}
            onClick={onClickScreen}
          >
            {message}
          </div>
          {renderAverage()}
        </>
      );
    };
    
    export default ResponseCheck;
    ResponseCheck.jsx(クラス)
    import React, { Component } from 'react';
    
    class ResponseCheck extends Component {
        state = {
            state: 'waiting',
            message: '클릭해서 시작하세요.',
            result: [],
        };
    
        timeout;
        startTime;
        endTime;
    
        onClickScreen = () => {
            const { state, message, result} = this.state;
            if (state === 'waiting') {
                this.setState({
                    state: 'ready',
                    message: '초록색이 되면 클릭하세요.',
                });
                this.timeout = setTimeout(()=>{
                    this.setState({
                        state: 'now',
                        message: '지금 클릭',
                    });
                    this.startTime = new Date();
                }, Math.floor(Math.random() * 1000) + 2000); // 2~3초 랜덤
            } else if (state === 'ready') { // 성급하게 클릭
                clearTimeout(this.timeout);
                this.setState({
                    state: 'waiting',
                    message: '너무 성급하시군요! 초록색이 된 후에 클릭하세요.',
                })
            } else if (state === 'now') {   // 반응속도 체크
                this.endTime = new Date();
                this.setState((prevState) => {
                    return {
                        state: 'waiting',
                        message: '클릭해서 시작하세요.',
                        result: [...prevState.result, this.endTime -  this.startTime],
                    };
                });
            }
        };  
    
        onReset = () => {
            const { result } = this.state;
            this.setState({
                result: [],
            });
        };
    
        renderAverage = () => {
            const { result } = this.state;
            return result.length === 0 
            ? null 
            : <> 
            <div>평균 시간: {result.reduce((a,c)=> a + c) / result.length}ms</div>
            <button onClick={this.onReset}>리셋</button>
            </>
        };
    
        render() {
            const { state, message } = this.state; 
            return (
                <>
                    <div
                        id="screen"
                        className={state}
                        onClick={this.onClickScreen}
                    >
                        {message}
                    </div>
                    {this.renderAverage()}
                </>
            );
        }
    }
    
    export default ResponseCheck;
    client.jsx
    import React from 'react';
    import ReactDOM from 'react-dom';
    
    import ResponseCheck from './ResponseCheck.jsx';
    
    ReactDOM.render(<ResponseCheck />, document.querySelector('#root'));
    index.html
    <!DOCTYPE html>
    <html lang="ko">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>반응속도체크</title>
        <style>
            #screen {
                width: 300px;
                height: 200px;
                text-align: center;
                user-select: none;
            }
            #screen.waiting { 
                background-color: aqua;
            }
            #screen.ready {
                background-color: red;
                color: white;
            }
            #screen.now {
                background-color: greenyellow;
            }
        </style>
    </head>
    <body>
        <div id="root"></div>
        <script src="./dist/app.js"></script>
    </body>
    </html>