React 스터디 4주차

🎮 웹 게임을 만들며 배우는 React
4주차: 반응속도 체크

1. 조건문

JSX 내부에서는 for(반복문)과 if(조건문)를 잘 사용하지 않는다.

조건문이 필요한 경우에는, 삼항 연산자를 사용한다.

{this.state.result.length === 0 
                ? null : 
                <div>평균 시간: {this.state.result.reduce((a, c) => a + c) / this.state.result.length}ms</div>
            }

또는, 보호 연산자의 사용이 가능하다.

                {this.state.result.length !== 0 
                &&
                <div>평균 시간: {this.state.result.reduce((a, c) => a + c) / this.state.result.length}ms</div>
            }


2. 반응속도체크(class)

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',
                    result: [...prevState.result, this.endTime - this.startTime],
                    message: '클릭해서 시작하세요'
                };
            });
        }
    }
    renderAverage = () => {
        const { result } = this.state;
        return this.state.result.length !== 0 
            &&
            <div>평균 시간: {this.state.result.reduce((a, c) => a + c) / this.state.result.length}ms</div>
    }

    render() {
        const { state, message } = this.state;
        return (
            <>
                <div id="screen" className={this.state.state}
                onClick={this.onClickScreen}>
                    {this.state.message}
                </div>
                {this.renderAverage()}
            </>
        )
    }
}

export default ResponseCheck;

3. 반응속도체크(Hooks)

class를 Hooks로 전환할 때, 다른 부분들은 이전에 했던 것처럼 변경해주면 된다. 이때 신경써야 할 부분은 timeout / startTime / endTime이다. 이들은 this의 속성이었으므로 useRef를 통해 표현해 주어야 하고, 사용할 때에는 timeout.current 와 같이 작성한다.

이때 useRef()는

  • .current 프로퍼티에 변경 가능한 값을 담고 있는 상자 역할을 함
  • 순수 자바스크립트 객체를 생성하고 매번 렌더링을 할 때 동일한 ref 객체를 제공
  • 가변값 유지에 있어 편리 (이때, .current 프로퍼티를 변형하는 것이 리렌더링을 발생시키지는 않는다. 즉, 값을 바뀌기는 하지만 화면에 영향을 미치게 하고 싶지 않을 때 Ref를 사용한다.)

와 같은 특징을 가지고 있다.

(참고: https://ko.reactjs.org/docs/hooks-reference.html#useref)

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(); // 화면은 바꾸고 싶지 X
    const endTime = useRef();

    const onReset = () => {
        setResult('');
    };

    const onClickScreen = () => { 
        if (state === 'waiting') {
            setState('ready');
            setMessage('초록색이 되면 클릭하세요');
            
            timeOut.current = setTimeout(() => {
                setState('now');
                setMessage('지금 클릭');

                startTime.current = new Date(); // Ref에 기록
            }, Math.floor(Math.random() * 1000) + 2000); // 2~3초 랜덤

        } else if (state === 'ready') { // 성급하게 클릭
            clearTimeout(timeOut);
            setState('waiting');
            setMessage('성급하시네요! 초록색이 된 후에 클릭하세요.');

        } else if (state === 'now') {
            endTime.current = new Date();
            setState('waiting');
            setResult((prevResult) => { 
                return [...prevResult, endTime.current - startTime.current];
            });
            setMessage('클릭해서 시작하세요');
        }
    }
    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;

4. return 내부에 for과 if 사용하기

JSX문에서 중괄호를 입력하면 Javascript 문법을 사용할 수 있는 것을 응용하여 return문 함수 내부에서 for과 if문을 사용할 수 있다.

    return (
        <>
            <div id="screen" className={state}
            onClick={onClickScreen}>
                {message}
            </div>
            {() => {
                if (result.length === 0) {
                    return null;
                } else {
                        return <>
                        <div>평균 시간: {result.reduce((a, c) => a + c) / result.length}ms</div>
                        <button onClick={onReset}>리셋</button>
                    </>
                }
            }}

            {renderAverage()}
        </>
    )

추가

1.

Uncaught TypeError: Cannot read properties of undefined (reading 'length')

        return (result.length === 0 
            ? null :
            <>
                <div>평균 시간: {result.reduce((a, c) => a + c) / result.length}ms</div>
                <button onClick={onReset}>리셋</button>
            </>)

이 코드에서 발생한 오류이다.

알고 보니 useState의 초깃값을 제대로 설정해 주지 않았다... 😑 사진과 같이 수정하니 제대로 동작하였다.

좋은 웹페이지 즐겨찾기