#4 ref의 응용 / 반응속도 테스트

sham·2021년 8월 22일
0

인프런에 올라간 제로초님의 강의를 보고 정리한 내용입니다.
https://www.inflearn.com/course/web-game-react


코드

클래스 버전

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);
		} else if (state == "ready") { // 성급하게 클릭
			clearTimeout(this.timeout);
			this.setState({
				state : "waiting",
				message : "성급하시군요!",
				result : [],
			})
		} else if (state == "now") { // 반응속도 체크
			this.endTime = new Date();
			this.setState((prevState) => {
				return {
					state : "waiting",
					message : "클릭해서 시작하세요.",
					result : [...prevState.result, this.endTime - this.startTime],
				} 	
			})
		}
	}
	renderAverage = () => {
		const {result} = this.state;
		console.log(result);
		return result.length === 0
		? null 
		: <div>평균 시간 : {result.reduce((a, c) => a + c) / result.length}ms</div>
	};

	Reset = () => {
		this.setState({
			result : [],
		})
	}
	render() {
		return (
			<>
			<div id="screen" className={this.state.state} onClick={this.onClickScreen}>
				{this.state.message}
			</div>
			{this.renderAverage()}
			<button onClick={this.Reset}>리셋</button>
			</>
		)
	}
}

export default ResponseCheck;

함수 버전

ResponseCheck.jsx

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") {
			setState("ready");
			setMessage("초록색이 되면 클릭하세요.");
			timeout.current = setTimeout(() => {
				setState("now");
				setMessage("지금 클릭!");
				startTime.current = new Date();
			}, Math.floor(Math.random() * 1000) + 2000);
		} 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 renderAverage = () => {
		return result.length === 0
		? null 
		: <div>평균 시간 : {result.reduce((a, c) => a + c) / result.length}ms</div>
	};

	const Reset = () => {
				setResult([]);
			}
	return (
		<>
			<div id="screen" className={state} onClick={onClickScreen}>
				{message}
			</div>		
			{renderAverage()}
			<button onClick={Reset}>리셋</button>
			</>
	)
}

#4-1 리액트 조건문

리액트에 컴포넌트의 render 안에서는 for, if를 쓸 수 없다.

  • 다른 방식으로 표현을 해야만 한다.
import React, {Component, useRef, useState, memo, createRef} from 'react';

class ResponseCheck extends Component {
	state = {
		state : "waiting",
		message : "클릭해서 시작하세요.",
		result : []
	};	
	render() {
		return (
			<>
			<div id="screen" className={this.state.state} onClick={this.onClickScreen}>
				{this.state.message}
			</div>
			{this.state.result.length === 0 ? null : <div>평균 시간 : {this.state.result.reduce((a, c) => a + c) / this.state.result.length}ms</div>
			}
			</>
		)
	}
}

export default ResponseCheck; 

배열이 비었을 때는 reduce함수를 사용할 수 없다.

삼항 연산자를 이용해 참일 때만 태그를 보여주도록 해야 한다.

  • {조건문 === 참 ? <div>태그</div> : null}

보호 연산자(&&)를 써주는 방법도 있다.

  • {조건문 === 참 && <div>태그</div>}
import React, {useRef, useState} from 'react';

class ResponseCheck extends Component {
	state = {
		state : "waiting",
		message : "클릭해서 시작하세요.",
		result : []
	};	

	renderAverage = () => {
		const {result} = this.state;
		return result.length === 0
		? null 
		: <div>평균 시간 : {result.reduce((a, c) => a + c) / result.length}ms</div>
	};
	render() {
		return (
			<>
			<div id="screen" className={this.state.state} onClick={this.onClickScreen}>
				{this.state.message}
			</div>
			{this.renderAverage()}
			</>
		)
	}
}

export default ResponseCheck;

메서드로 빼주는 방법도 있다.

클래스 컴포넌트의 메서드는 항상 화살표 함수!!!

  • 함수 컴포넌트

    const ResponseCheck = () => {
    	const [status, setStatus] = useState("waiting");
    	const [message, setMessage] = useState("클릭해서 시작하세요.");
    	const [result, setResult] = useState([]);
    	
    	const onClickScreen = () => {
    		
    	}
    	const renderAverage = () => {
    		return result.length === 0
    		? null 
    		: <div>평균 시간 : {result.reduce((a, c) => a + c) / result.length}ms</div>
    	};
    	return (
    		<>
    			<div id="screen" className={status} onClick={onClickScreen}>
    				{message}
    			</div>
    			{renderAverage()}
    			</>
    	)
    }

reduce()

배열.reduce((누적값, 현잿값, 인덱스, 요소) => { return 결과 }, 초깃값);

누적값이기 때문에 다양하게 활용할 수 있다.

oneTwoThree = [1, 2, 3];
result = oneTwoThree.reduce((acc, cur, i) => {
  console.log(acc, cur, i);
  return acc + cur;
}, 0);
// 0 1 0 현재까지 누적된 값은 0, 현재 가지고 있는 요소는 1, 해당 요소의 인덱스는 0번째
// 1 2 1 현재까지 누적된 값은 1(0 + 1), 현재 가지고 있는 요소는 2, 해당 요소의 인덱스는 1번째
// 3 3 2 현재까지 누적된 값은 3(1 + 2), 현재 가지고 있는 요소는 3, 해당 요소의 인덱스는 2번째
console.log(result); // 6

#4-2 setTimeout 넣어 반응속도체크

this.state, this.props같이 자주 쓰이는 문법들은 처음부터 구조 분해를 해주는 편이 낫다.

import React, {Component, useRef, useState, memo, createRef} from 'react';

class ResponseCheck extends Component {
	state = {
		state : "waiting",
		message : "클릭해서 시작하세요.",
		result : []
	};	

	timeout;
	onClickScreen = () => {
		const {state, message, result} = this.state;
		if (state === "waiting") {
			this.setState({
				state : "ready",
				message : "초록색이 되면 클릭하세요.",
			});
			this.timeout = setTimeout(() => {
				this.setState({
					state : "now",
					message : "지금 클릭!"
				})
			}, Math.floor(Math.random() * 1000) + 2000);
		} else if (state == "ready") { // 성급하게 클릭
			clearTimeout(this.timeout);
			this.setState({
				state : "waiting",
				message : "성급하시군요!",
				result : [],
			})
		} else if (state == "now") { // 반응속도 체크
			this.setState({
				state : "waiting",
				message : "클릭해서 시작하세요.",
				result : [],
			})
		}
	
	}
	renderAverage = () => {
		const {result} = this.state;
		return result.length === 0
		? null 
		: <div>평균 시간 : {result.reduce((a, c) => a + c) / result.length}ms</div>
	};
	render() {
		return (
			<>
			<div id="screen" className={this.state.state} onClick={this.onClickScreen}>
				{this.state.message}
			</div>
			{this.renderAverage()}
			</>
		)
	}
}

export default ResponseCheck;

클릭했을 때 상태에 따라 다른 div를 출력.

setTimeout이 실행되는 중 ready 상태일 때 클릭을 해 처음으로 돌아가도 setTimeout는 state를 now로 바꾸게 된다. 이를 막기 위해 클래스 내부의 객체(?)에 함수를 대입하고 clearTimeout으로 해당 함수의 실행을 멈추게 해야 한다.

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);
		} else if (state == "ready") { // 성급하게 클릭
			clearTimeout(this.timeout);
			this.setState({
				state : "waiting",
				message : "성급하시군요!",
				result : [],
			})
		} else if (state == "now") { // 반응속도 체크
			this.endTime = new Date();
			this.setState((prevState) => {
				return {
					state : "waiting",
					message : "클릭해서 시작하세요.",
					result : [...prevState.result, this.endTime - this.startTime],
				} 	
			})
		}
	}
	renderAverage = () => {
		const {result} = this.state;
		console.log(result);
		return result.length === 0
		? null 
		: <div>평균 시간 : {result.reduce((a, c) => a + c) / result.length}ms</div>
	};
	render() {
		return (
			<>
			<div id="screen" className={this.state.state} onClick={this.onClickScreen}>
				{this.state.message}
			</div>
			{this.renderAverage()}
			</>
		)
	}
}

new Date로 시간을 담는 객체를 생성. state가 now로 바뀐 시점부터 시간을 잰다. state로 만들게 되면 값을 잴 때마다 setState로 값을 바꾸게 되서 렌더링을 하게 된다.

  • 함수 컴포넌트

    const ResponseCheck = () => {
    	const [state, setState] = useState("waiting");
    	const [message, setMessage] = useState("클릭해서 시작하세요.");
    	const [result, setResult] = useState([]);
    	
    	let timeout;
    	let startTime;
    	let endTime;
    	const onClickScreen = () => {
    		if (state === "waiting") {
    			setState("ready");
    			setMessage("초록색이 되면 클릭하세요.");
    
    			timeout = setTimeout(() => {
    				setState("now");
    				setMessage("지금 클릭!");
    				startTime = new Date();
    			}, Math.floor(Math.random() * 1000) + 2000);
    		} else if (state == "ready") { // 성급하게 클릭
    			clearTimeout(timeout);
    			setState("waiting");
    			setMessage("성급하셨군요!");
    			setResult([]);
    		} else if (state == "now") { // 반응속도 체크
    			endTime = new Date();
    			setState("waiting");
    			setMessage("클릭해서 시작하세요.");
    			setResult((prevState) =>  {return [...prevState, endTime - startTime]});
    		}
    	}
    	const renderAverage = () => {
    		return result.length === 0
    		? null 
    		: <div>평균 시간 : {result.reduce((a, c) => a + c) / result.length}ms</div>
    	};
    	return (
    		<>
    			<div id="screen" className={state} onClick={onClickScreen}>
    				{message}
    			</div>
    			{renderAverage()}
    			</>
    	)
    }

setTimeout()

setTimeout(function() { //함수 코드}, delay);

delay(정수) 밀리초가 지나면 첫번째 인자인 함수를 실행하게끔 한다.

setTimeout(() => { //함수 코드}, delay);

setTimeout(() => { //호출될 (콜백)함수}, delay);

화살표 함수 방식이나 아예 함수를 인자로 집어넣기도 한다.


#4-3 성능 체크와 Q&A

Reset = () => {
		this.setState({
			result : [],
		})
	}
	render() {
		return (
			<>
			<div id="screen" className={this.state.state} onClick={this.onClickScreen}>
				{this.state.message}
			</div>
			{this.renderAverage()}
			<button onClick={this.Reset}>리셋</button>
			</>
		)
	}
}

버튼 기능 추가.

기능을 메서드(함수)로 분리하기보다는 자식 컴포넌트를 만드는 편이 더 좋다.

성능 개선

render()가 실행되면, 즉 해당 컴포넌트의 state에서 바뀌는 부분이 하나라도 존재한다면 render 내부의 모든 것들이 다시 렌더링된다.

함수 컴포넌트에서는 hook로 설정한 state값이 바뀌면 함수 전체가 실행된다.

즉, 바뀌는 부분(state)이 하나라도 존재한다면 부모 컴포넌트에서 pureComponent를 적용하는 것은 의미가 없다.

그러므로 바뀌지 않는 부분들로 자식 컴포넌트를 만들어서 퓨어 컴포넌트로 만들어주어야만 한다.


#4-4 반응속도체크 / Hooks 전환

import React, {Component, PureComponent, useRef, useState, memo, createRef} 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") {
			setState("ready");
			setMessage("초록색이 되면 클릭하세요.");
			timeout.current = setTimeout(() => {
				setState("now");
				setMessage("지금 클릭!");
				startTime.current = new Date();
			}, Math.floor(Math.random() * 1000) + 2000);
		} 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 renderAverage = () => {
		return result.length === 0
		? null 
		: <div>평균 시간 : {result.reduce((a, c) => a + c) / result.length}ms</div>
	};

	const Reset = () => {
				setResult([]);
			}
	return (
		<>
			<div id="screen" className={state} onClick={onClickScreen}>
				{message}
			</div>
			{renderAverage()}
			<button onClick={Reset}>리셋</button>
			</>
	)
}

export default ResponseCheck;

함수형 컴포넌트로 전환

시간 체크를 위한 timeout, startTime, endTime

  • 클래스 - this의 속성
  • 함수 - ref 사용

클래스에서 this로 표현한 속성들을 함수(hooks)에서는 ref로 표현한다.

ref를 사용할 때는 항상 current를 사용한다.(통해 접근한다.)

state와 ref의 차이

state를 수정(setState)하면 return 부분이 렌더링되어 함수 전체가 렌더링되지만 ref의 값을 변경해도 다시 렌더링 되지 않는다.

값을 바꾸되 화면에는 영향을 끼치고 싶지 않을 때 Ref를 사용한다.

Ref에 대입하는 구문에서 setState를 사용하더라도 화면은 변하지 않는다.


#4-5 return 내부에 for, if 사용

jsx에서 중괄호({})를 치면 자바스크립트 코드를 쓸 수 있다는 점, 함수에서는 if를 쓸 수 있다는 점을 응용해 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>
				}
			})()} {/* 즉시 실행해야 하기 때문에 ()붙여야 한다.*/}
			{/* {renderAverage()} */}
			<button onClick={Reset}>리셋</button>
			</>
	)

이렇게 할 바에야 그냥 함수로 빼는 편이 훨씬 낫다...

map대신 for을 쓰고 싶다면 위의 예처럼 즉시 실행 함수를 만들면 된다.

{(() => {
const array = [];
for (let i = 0; i < tries.length; i++) {
	array.push(<태그></태그>);
}
	return array;
}
})()};

다시 말하지만 함수로 빼거나 자식 컴포넌트로 만드는 게 가장 좋다...

return [
	<div key="apple">apple</div>
	<div key="peach">peach</div>
	<div key="orange"orange</div>
]

배열에 jsx 담아서 return하는 것은 유효한 문법이다!

항상 키를 붙여주어야 한다. 많이 쓰이는 문법은 아니다.

profile
씨앗 개발자

0개의 댓글

관련 채용 정보