React, Next, Redux/🚀 React with Hooks

React Hooks : useState, 비동기적 속성과 함수형 updator

DarrenKwonDev 2020. 3. 20. 21:53

리액트 공식 문서에서 설명한 기본 Hook은 알아두는 것이 좋다.

 

 

🚓 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

 

velog.io/@cada/React%EC%9D%98-setState%EA%B0%80-%EC%9E%98%EB%AA%BB%EB%90%9C-%EA%B0%92%EC%9D%84-%EC%A3%BC%EB%8A%94-%EC%9D%B4%EC%9C%A0