React Hooks : useState, 비동기적 속성과 함수형 updator
🚓 hooks가 뭐야
class componenet에서 state, ref를 사용하는 걸 functional components에도 사용할 수 있게 만든 거다. (매우 간단!)
따라서 class components를 사용하지 않아도 되었고 React 측에서도 Hook의 사용을 권장한다.
=> 함수형 프로그래밍 스타일로 코딩 가능해짐
다음은 우리가 줄 곧 사용해 온 일반적인 클래스 컴포넌트이다.
React.Component에서 extends 해주고 state 만들어주고 state를 수정할 경우에는 this.setState({})를 사용하며 컴포넌트의 라이프 사이클에 맞춰서 render()가 실행된다.
위와 같은 클래스 컴포넌트를 Hook으로 고쳐보았다.
useState를 이용하여 state를 수정하기 쉽게 만들었다. 한눈에 보아도 코드의 양이 줄어든 것을 알 수 있다.
클래스 컴포넌트에서 state를 클래스 초반에 모두 몰아서 적어주듯, useState 또한 여러 개를 처음에 지정한다.
예시를 들자면 이렇다.
const [first, setFirst] = useState(Math.ceil(Math.random() * 9));
const [second, setSecond] = useState(Math.ceil(Math.random() * 9));
const [value, setValue] = useState(""); // 입력한 값
const [output, setOutput] = useState(); // 결과
🚗 useState
위의 예시에서 const [count, setCount] = useState(0); 의 의미를 좀 알아보자.
useState는 항상 2개의 요소가 있는 array이다. 첫번째는 value, 두번째는 value를 변경하는 방법을 의미한다.
또한, useState(0); 은 첫번째 value의 값이 0임을 의미한다.
🚕 useState를 이용해 Hook 만들기
단순히 useState를 사용하는 것이 아니라 이를 이용해 자체적으로 Hook을 만들어서 사용할 수 있다. 남이 만들어 놓은 Hook을 npm을 이용해 설치해서 사용하는 것도 가능하다.
useState의 비동기적 속성
속성 1. setTime으로 기다리는 건 의미 없다. 리렌더링이 되어야 변화된 값을 참고할 수 있게 된다.
위에서 살펴본 코드도 setState을 한 직후에 곧바로 state를 확인할 수 없는 것도 이러한 비동적인 작동 방식 때문인데, 동기적(Sync)이 아니기 때문에 반영된 후에 state를 보여주는 게 아니라 일단 코드를 그대로 진행시킬 것입니다. 따라서 useState를 쓴다고 해서 곧바로 변경 내역이 반영되지 않을 것입니다.
그렇다면 임의적으로 setTimeout을 통해 몇 초 기다렸다가 state를 출력해본다면 어떻게 될까요? 그래도 state를 출력하지 않습니다.
그렇다면 변경은 언제 반영 되느냐, state updates will reflect in the next re-render by which the existing closures are not affected but new ones are created 즉, 다시 렌더될 때라는 군요.
정리하자면, React의 특성상 state가 바뀌면 재 렌더링 된다. => 재 렌더링 될 때 바뀐 state가 반영된다.
속성 2. 대기열적으로 state 변화를 일괄적으로 적용하기 때문에 함수형 updator를 사용해야 한다.
만약 state 변경이 Sync 동기적으로 작동한다면 state의 변경이 한꺼번에 여러번 많이 발생하게 될경우 그 만큼 많은 렌더링이 많이 발생하게 되어 유저는 갑작스런 멈춤을 경험하게 될 것입니다. 이를 방지하기 위해 state의 변경사항을 즉시 반영하지 않고 변경사항을 Queue에 넣었다가 일괄 적용 시킵니다.
=> '일괄 적용'이기 때문에 아래와 같은 예시에서 값을 두 번 변경하지 않고 한 번만 변경한 것처럼 보입니다.
function App() {
const [count, setCount] = useState(0);
// 이 함수의 결과가 정상적으로 작동하지 않음을 알 수 있습니다.
const Button2Times = () => {
setCount(count + 1);
setCount(count + 1);
};
return (
<div>
<span>{count}</span>
<button onClick={Button2Times}>should + 2</button>
</div>
);
}
좀 더 실전적인 예시로는 다음 결론을 볼 수 있다.
function App() {
const [isDisabled, setIsDisabled] = useState(false);
// 이전 값에 의존하는 상태갱신 함수에 갱신결과값만을 전달하면
const toggleButton = () => setIsDisabled((isDisabled) => !isDisabled);
// 이 함수의 결과가 정상적으로 작동하지 않음을 알 수 있습니다.
const toggleButton2Times = () => {
for (let i = 0; i < 2; i++) {
// 버튼을 2번 누르는 것이니 state가 그대로여야 하는데 결과는 그렇지 않음.
toggleButton();
}
};
return (
<div>
<button disabled={isDisabled}>
I'm {isDisabled ? "disabled" : "enabled"}
</button>
<button onClick={toggleButton}>Toggle button state</button>
<button onClick={toggleButton2Times}>Toggle button state 2 times</button>
</div>
);
}
위 함수를 의도대로 동작하게 만들기 위해서는 전의 state값을 참고하여 업데이트하는 함수형 updator를 만들어야 한다.
그래서 어떻게 하면 될까? 아래처럼 하면 된다.
// Bad
const Button2Times = () => {
setCount(count + 1);
setCount(count + 1);
};
// Good
const Button2Times = () => {
setCount((count) => count + 1);
setCount((count) => count + 1);
};
// Bad
const toggleButton = () => setIsDisabled(!isDisabled);
// Good
const toggleButton = () => setIsDisabled((isDisabled) => !isDisabled);
reference)
www.youtube.com/watch?v=DlgEF077sP4