본문 바로가기
💻CODING/react. vue

[react] react로 반응속도 테스트 게임 만들기(ft. useRef, 조건문, spread 연산자)

by 코딩하는 갓디노 2021. 3. 24.

반응속도 테스트

 

리액트 class/hooks로 구현한
반응속도 테스트 게임 입니다.

 

예제는 인프런의 제로초, "조현영"님의 강의를 들으면서 공부한 내용입니다.

 

client.jsx

import React from 'react';
import ReactDom from 'react-dom';
import ResponseCheck from './ResponseCheck';

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: 400px;
            height: 300px;
            text-align: center;
            user-select: none;
            /*해당요소의 드래그, 더블클릭, 블럭지정을 막는다.*/
            font-size: 20px;
            font-weight: bold;
        }

        #screen.waiting {
            background: skyblue;
        }

        #screen.ready {
            background: pink;
        }

        #screen.go {
            background: greenyellow;
        }
    </style>
</head>

<body>
    <div id="root"></div>
    <script src="./dist/app.js"></script>
</body>

</html>

 

ResponseCheck.jsx - class 방식

import React, { Component } from 'react';

class ResponseCheck extends Component {
    state = {
        screenState: 'waiting', 
        message: 'Click to start',
        resultTime: [],
    }

    timeout;
    startTime; //state로 넣지않음(state는 렌더링이 되므로)
    endTime; //state로 넣지않음

    onClick = () => { //속성
        const { screenState, message, resultTime } = this.state;
        if ( screenState === 'waiting') {
            this.setState({
                screenState: 'ready', 
                message: 'wait for green',
            });
            //비동기 함수
            this.timeout = setTimeout(()=>{ //clearTimeout 위해 timeout 키워드 설정
                this.setState({
                    screenState: 'go',
                    message: 'Click now'
                });
                this.startTime = new Date(); //시작 시작
            }, Math.floor(Math.random() * 1000) + 2000); //2-3초 사이
        } else if (screenState === 'ready') { //ready화면에서 클릭(부정 클릭) 방지
            clearTimeout(this.timeout); //setTimeout초기화, 안하면 작동(raedy 없이 바로 go화면)   
            this.setState({
                screenState: 'waiting', //이전 화면으로 돌아가기
                message: 'you clicked too early', 
            });
        } else if (screenState === 'go') { //반응속도 체크
            this.endTime = new Date();  //끝 시작
            this.setState((prev) => { //함수형 setState로 (resultTime 때문에)
                return {
                    screenState: 'waiting',
                    message: 'Click to start',
                    //push 대신 함수형 setState 사용해서 새로운 데이터 추가
                    resultTime: [ ...prev.resultTime, this.endTime - this.startTime],
                } 
            })
        }
    };

    renderAvg = () => { //함수
        return this.state.resultTime.length === 0
        ? null //화면에 보이지 않음
        : 
        <>
            <div>평균시간: {this.state.resultTime.reduce((a, c)=>a+c) / 
            this.state.resultTime.length}ms</div>
            <button onClick={this.onClickReset}>리셋</button>
        </>
    }

    onClickReset = () => { //속성
        this.setState({
            resultTime: [],
        });
    }

    render() {
        return (
            <>
             <div
                id="screen"
                className={this.state.screenState}
                onClick={this.onClick}
                >
                 {this.state.message}
             </div>
             {this.renderAvg()}      
            </>
        ); 
    }
}

export default ResponseCheck;

 

ResponseCheck.jsx - hooks 방식

import React, { useState, useRef } from 'react';

const ResponseCheck = () => {
    const [screenState, setScreenState] = useState('waiting');
    const [message, setMessage] = useState('click to start');
    const [resultTime, setResultTime] = useState([]);

    //hooks에서는 this의 속성들을 ref로 표현
    const timeout = useRef();
    const startTime = useRef(); //state로 넣지않음(state는 렌더링이 되므로)
    const endTime = useRef(); //state로 넣지않음

    const onClick = () => {
        if ( screenState === 'waiting') {
            setScreenState('ready');
            setMessage('waiting for green');
            //비동기 함수
            timeout.current = setTimeout(()=>{ //clearTimeout 위해 timeout 키워드 설정
                setScreenState('go');
                setMessage('Click now');
                startTime.current = new Date(); //시작 시작
            }, Math.floor(Math.random() * 1000) + 2000); //2-3초 사이
        } else if (screenState === 'ready') { //ready화면에서 클릭(부정 클릭) 방지
            clearTimeout(timeout.current); //setTimeout초기화
            setScreenState('waiting');
            setMessage('you clicked too early');             
        } else if (screenState === 'go') { //반응속도 체크
            endTime.current = new Date();  //끝 시작
            setScreenState('waiting');
            setMessage('Click to start');
            setResultTime((prev) => {
                return [...prev, endTime.current - startTime.current];
            });
        }
    }

    const renderAvg = () => {
        return resultTime.length === 0
        ? null //화면에 보이지 않음
        : 
        <>
            <div>평균시간: {resultTime.reduce((a, c)=>a+c) / resultTime.length}ms</div>
            <button onClick={onClickReset}>리셋</button>
        </>
    }

    const onClickReset = () => {
        setResultTime([]);
    }

    return (
        <>
         <div
            id="screen"
            className={screenState}
            onClick={onClick}
            >
             {message}
         </div>
         {renderAvg()}      
        </>
    ); 
}

export default ResponseCheck;

 

useRef 

hooks에서는 this의 속성들을 ref로 표현합니다. 특히 input에 사용합니다. 

useState, useRef 차이
useState는 setState로 변경될때마다 return 부분이 렌더링되고,
useRef는 렌더링이 안됩니다. (화면에 영향을 미치지 않는 데이터)

 

조건문: 삼항연산자

react에서는 for문, if문을 사용할수 없습니다. (이유: jsx파일에서 사용 못함.)
조건문이 필요한 경우, 삼항 연산자를 주로 사용합니다. 

 

spread 연산자(스프레드 연산자)

react에서는 불변성을 지켜야하기 때문에 push 사용할 수가 없습니다

여기서 불변성이란 기존의 값을 그대로 유지하면서 새로운 값을 추가하는 것으로
객체가 생성된 이후  상태를 변경할  없는 디자인 패턴을 의미합니다.

react 로직은 기존 state와 새로운 state를 비교하여 다를 경우에만 재 렌더링합니다. 

push를 사용하면 기존 배열에서 추가 되면서 기존 state와 새로운 state는 같은 것이기 때문에 react에서 재 랜더링을 하지 못합니다.

즉, push는 원본의 배열 데이터가 수정되고, concat 또는 스프레드 연산자는 원본의 데이터가 그대로 있습니다. 


그래서 spead 연산자를 사용하여 새로운 배열을 만들어줘야 합니다. 
즉, 새로운 배열의 요소에 spread 연산자로 기존의 값을 가져온 뒤에 바뀌는 부분만(새로운 객체) 추가를 해줍니다. 

spread 연산자를 이용해 객체나 배열의 값을 복사할때는 얕은 복사(참조) 하게 됩니다.
이렇게 불변성을 유지함으로써 side-effect 줄어들고 컴포넌트 최적화가 가능합니다.

또한 기존의 state에서 새로운 state를 만들 경우 함수형 setState 사용합니다. 

spread 연산자 예제

 //class 
this.setState((prevState) => {
	return {
		tries: […prevState.tries, { try: this.state.value, result: true}]
	}
} ) 

//hooks
setTries(
	(prevState) => […prevState, { try: value, result: true }]
)
반응형

댓글