
자, 파란색 화면을 누르면 주황색 화면이 나온다. 이때, '초록색이 되면 눌러주세요'라는 문구와 함께 랜덤한 시간이 지나면, 초록색 화면을 누른다! 초록색 화면을 빠르게 누르고 나면,
이렇게 나의 평균 시간이 나오게 된다.
단 한번 한 결과가 아니라 내가 5번 했으면, 5번 한 값이 평균을 구해서 평균 시간을 보여주는 것이다!
코드를 작성하기 전, state에 정의할 부분을 찾아야 한다
우선, 상자의 색깔 (상태에 따라 변하기 때문에 : state), 상자 속 메시지 (message), 평균 시간을 계산한 값(result)로 정의할 수 있다.
import React, { Component } from 'react';
class ResponseChecked extends Component {
state = {
state: 'waiting',
message: '클릭해서 시작하세요',
result: [],
};
setTime;
startTime;
endTime;
onClickScreen = () => {
const { state } = this.state;
if (state === 'waiting') {
this.setState({
state: 'ready',
message: '초록색으로 변하면 클릭하세요',
});
this.setTime = setTimeout(() => {
this.setState({
state: 'now',
message: '지금 누르세요!',
});
this.startTime = new Date(); // 시작 시간 설정
}, Math.floor(Math.random() * 1000) + 2000);
} else if (state === 'now') {
this.endTime = new Date(); // 종료 시간 설정
this.setState((prevState) => ({
state: 'waiting',
message: '클릭해서 시작하세요',
result: [...prevState.result, this.endTime - this.startTime], // 결과 추가
}));
} else if (state === 'ready') {
clearTimeout(this.setTime);
this.setState({
state: 'waiting',
message: '성급! 초록색 화면이 나오면 눌러주세요 !',
});
}
};
renderAverage = () => {
const { result } = this.state;
return result.length === 0 ? null : (
<div>평균 시간: {result.reduce((a, c) => a + c) / result.length} ms</div>
);
};
render() {
const { state, message } = this.state;
return (
<>
<div id="screen" className={state} onClick={this.onClickScreen}>
{message}
</div>
{this.renderAverage()}
</>
);
}
}
export default ResponseChecked;

변할 값들을 state 안에 정해두었다. state 는 'waiting'을 초기값으로,
message 는 '클릭해서 시작하세요', result는 빈 배열을 초기값으로 지정해 두었다.

className 속성을 {state}로 지정함으로써 해당 요소의 클래스를 동적으로 변경할 수 있다.
이 경우, state 값에 따라 해당 요소의 클래스가 변경되므로, 해당 요소의 스타일이 변화한다. --> 이것은 특히 UI의 상태에 따라 다른 스타일을 적용하고 싶을 때 유용하다.

이제 onClickScreen() 함수에 대해서 자세하게 살펴보자
여기서 중요한 점은 state가 ready 일 때, now 일 때, waiting 일 때 로 state를 설정하고, css도 변화한다는 것이다.
'waiting' 상태일 때:
상태를 'ready'로 변경하고, 메시지를 '초록색으로 변하면 클릭하세요'로 설정한다.
일정 시간 후 (랜덤) 에 상태를 'now'로 변경하고, 메시지를 '지금 누르세요!'로 설정한다.
이 때의 시간을 startTime으로 기록
'now' 상태일 때:
사용자가 클릭한 시간을 기록하고, 'now' 상태에서 클릭까지 걸린 시간을 계산한다.
상태를 'waiting'으로 변경하고, 메시지를 '클릭해서 시작하세요'로 설정한다.
클릭까지 걸린 시간을 결과에 추가한다.
'ready' 상태일 때:
기존에 설정한 타이머를 취소하고, 상태를 'waiting'으로 변경하며, 메시지를 '성급! 초록색 화면이 나오면 눌러주세요 !'로 설정한다.
요런 느낌 ...?
까먹을 뻔 했던 중요한 코드

반응 시간의 평균을 표시하는 함수인 renderAverage를 정의하고 있다.
result 배열의 길이가 0이면, 즉 클릭 결과가 없으면 null을 반환한다. 이 경우에는 아무 것도 표시되지 않는다.
result 배열에 클릭 결과가 있는 경우, 배열의 요소들을 모두 더한 후 배열의 길이로 나누어 평균을 구한다. 이를 통해 사용자의 평균 반응 시간을 계산한다.
지금까지 한 것 중엔 가장 코드 짜기 수월했던 것 같다 ...(?) 근자감이다 💪🏻💪🏻💪🏻💪🏻💪🏻💪🏻💪🏻💪🏻
자 여기서, 저 renderAverage()함수를 따로 class로 뽑아보면 어떨까 ?

요렇게 뽑아 보았다.

우선, <RenderAverage> 클래스를 불러오면서 (여기서 RenderAverage는 자식 컨포넌트가 된다)
<RenderAverage>에서 사용할 result와 onReset에 대해서 정의해 두었다(props로 전달한다)
이 정의해 둔 것들을 <RenderAverage> 에서 const {result, onReset} = this.props 로 받아온 뒤, 삼향연산자를 통해서 조건문을 실행한다.
실무에서는 요렇게 자주 뽑아서 사용한다고 한다!
한번 연습삼아서 뽑아보았다
import React, { useState, useRef } from 'react';
import RenderAverage from './renderAverage';
const ResponseChecked = () => {
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) => [
...prevResult,
endTime.current - startTime.current,
]);
}
};
const onReset = () => {
setResult([]);
setState('waiting');
setMessage('클릭해서 시작하세요');
};
return (
<>
<div id="screen" className={state} onClick={onClickScreen}>
{message}
</div>
<RenderAverage key={result.length} result={result} onReset={onReset} />
</>
);
};
export default ResponseChecked;
확실히 코드 자체가 길이가 많이 짧아진 느낌이 들긴 한다.
크게 달라진 부분은 없지만,

useRef부분을 잘 살펴봐야 한다.
왜 useState를 사용하지 않고, useRef를 사용했을까???
useState
useRef
.current라는 속성을 가지고 있으며, 컴포넌트가 다시 렌더링되어도 참조 객체의 값이 유지된다. 그러나 값이 변경되어도 컴포넌트가 다시 렌더링되지 않는다..current 로 접근해야 한다!!!!!!