Today I Learned ... react.js
🙋♂️ React.js Lecture
🙋 My Dev Blog
React Lecture CH 4
1 - 리액트 조건문
2 - setTimeout (반응속도게임)
3 - 성능체크
4 - 반응속도게임 (Hooks)
5 - return 내부에 for, if 사용(제어문)-
render() {
const { state, message } = this.state;
return (
<>
<div id="screen" className={state} onClick={this.onClickScreen}>
{message}
</div>
{this.renderAverage()} // 👈 이부분
</>
);
}
// this.renderAverage
renderAverage = () => {
const { results } = this.state;
return results.length === 0 ? null : ( // ❗️ return 붙여줘야함.
<>
평균 시간:
{results.reduce((prev, cur) => prev + cur) / results.length}
ms
<button onClick={this.onReset}>리셋</button>
</>
);
};
지난시간 Try 컴포넌트로 빼줬던 것 처럼!
renderAverage = () => {
const { results } = this.state;
return results.length === 0 ? null : (
<>
평균 시간:
{results.reduce((prev, cur) => prev + cur) / results.length}
ms
<button onClick={this.onReset}>리셋</button>
</>
);
};
onReset = () => {
this.setState({
results: [],
});
};
...
onReset = () => {
this.setState({
results: [], // state 변경 - render() 실행
});
};
renderAverage = () => {
const { results } = this.state;
return results.length === 0 ? null : (
<>
평균 시간:
{results.reduce((prev, cur) => prev + cur) / results.length}
ms
<button onClick={this.onReset}>리셋</button>
</>
);
};
// 버튼 onClick시 - onReset 실행 - setState - render() 실행 - 전체가 다시 렌더링됨
render() {
const { state, message } = this.state;
return (
<>
<div id="screen" className={state} onClick={this.onClickScreen}>
{message}
</div>
{this.renderAverage()}
</>
);
}
}
export default ResponseCheck;
따라서, screen 부분과 renderAverage 부분은 따로 별개의 컴포넌트로 구분해야한다.
✅ 참고
함수형 컴포넌트는 전체가 다시 실행되지만,
클래스형 컴포넌트는 render()부분만 다시 실행됨.
-> 함수형에서는 불필요한 함수의 재실행을 막기 위해
useCallback
,useMemo
등을 이용해야함.
// Hooks
import React, { useState, useRef } from 'react';
const ResponseCheck = () => {
const [state, setState] = useState('waiting');
const [message, setMessage] = useState('클릭해서 시작하세요');
const [results, setResults] = 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); // 2sec-3sec 사이 랜덤으로
} else if (state === 'ready') {
clearTimeout(timeout.current);
setState('waiting');
setMessage('너무 빠릅니다. 초록색이 된 후에 클릭하세요.');
} else if (state === 'now') {
endTime.current = new Date();
setState('waiting');
setMessage('클릭해서 시작하세요.');
setResults((prevResults) => [
...prevResults,
endTime.current - startTime.current,
]);
}
};
const onReset = () => {
setResults([]);
};
const renderAverage = () => {
return results.length === 0 ? null : (
<>
평균 시간:
{results.reduce((prev, cur) => prev + cur) / results.length}
ms
<button onClick={onReset}>리셋</button>
</>
);
};
return (
<>
<div id="screen" className={state} onClick={onClickScreen}>
{message}
</div>
{renderAverage()}
</>
);
};
export default ResponseCheck;
KEY POINT
- React.useState로 state 구현
this
모두 제거 - hooks에서는 안쓰임.- ⭐️ useRef()의 또다른 용도
-> 1. DOM에 접근하는 용도 (ex> focus() 등)
-> 2. ❗️ 렌더링이 일어나지 않는 변하는 값.
timeout
, startTime
, endTime
은 state가 아닌 클래스 몸체에 선언했었다.class ResponseCheck extends Component {
state = {
state: 'waiting',
message: '클릭해서 시작하세요',
results: [],
};
// 이 부분 🔻
timeout;
startTime;
endTime;
...
}
state
Hooks에서는 클래스가 아니므로, 프로퍼티로 선언할 수는 없다. (this.___ 불가)
Hooks에서는 대신 useRef()
를 사용하면 된다.
let inputRef = useRef(null);
<input ref={inputRef} />
// 사용시
inputRef.current.focus();
❗️ 주의
- useRef()를 사용할때 or 값을 변경할때는 반드시
current를 붙여줘야 한다!
참고 - current 프로퍼티 (react.org document)
render() 안에서 ref가 엘리먼트에게 전달되었을 때, 그 노드를 향한 참조는 ref의 current 어트리뷰트에 담기게 됩니다.
const node = this.myRef.current;
ref의 값은 노드의 유형에 따라 다릅니다.
ref 어트리뷰트가 HTML 엘리먼트에 쓰였다면, 생성자에서 React.createRef()로 생성된 ref는 자신을 전달받은 DOM 엘리먼트를 current 프로퍼티의 값으로서 받습니다.
ref 어트리뷰트가 커스텀 클래스 컴포넌트에 쓰였다면, ref 객체는 마운트된 컴포넌트의 인스턴스를 current 프로퍼티의 값으로서 받습니다.
함수 컴포넌트는 인스턴스가 없기 때문에 함수 컴포넌트에 ref 어트리뷰트를 사용할 수 없습니다.
-> '비제어 컴포넌트'를 제어할 때.
'비제어 컴포넌트' 란?
- 리액트가 제어하지 않는 컴포넌트.
- 순수 JS로만 제어함.
// map을 이용한 반복문
return(
<ul>
{tries.map((v, index) => (
<Try key={`${index}차시도`} tryInfo={v} />
))}
</ul>
)
// 삼항연산자를 이용한 조건문
return loading ? null : (
<>
<Contents />
</>
)
즉시실행함수(IIFE) 안에 if, for문을 작성할 수 있다.
return (
<>
<div id="screen" className={state} onClick={onClickScreen}>
{message}
</div>
{renderAverage()}
{(() => {
if (results.length === 0) {
return null;
} else {
return (
<>
평균 시간:
{results.reduce((prev, cur) => prev + cur) / results.length}
ms
<button onClick={onReset}>리셋</button>
</>
);
}
})()}
반복문의 경우에는, 각 요소에 해당하는 JSX를 저장할 array 변수를 선언하여 push한 후, 해당 array를 리턴해야 한다.
(기존에는 배열 메서드인 map으로 알아서 순회했지만, for문으로 바꿔야 하므로)
{(() => {
const array = [];
for (let i = 0; i < tries.length; i++) {
array.push(<Try key={`${index}차시도`} tryInfo={v} />)
}
return array;
})()}
-> ✅ 이와 같이 배열 안에 JSX를 담아 리턴하는 것도 유효한 문법이다!
예>
// 배열 리턴도 가능!
// ❗️ 단, key를 적어줘야한다.
return [
<div key="apple">사과</div>,
<div key="banana">바나나</div>,
<div key="strbr">딸기</div>,
<div key="grape">포도</div>,
<div key="kiwi">키위</div>,
];
-> 잘 안씀. 여러 태그를 묶어줄 떄에는 <></>
를 더 많이 쓴다.
주의
- 왠만해서는 if, for을 쓸거면 이런식으로 return 안에 쓰지 말고,
함수로 빼던가 자식 컴포넌트로 분리해주자.