개인공부/궁금해서 찾아보는

useEffect 완벽가이드 정독하기2

soon327 2021. 6. 23. 00:25

이 글은 아래의 글을 읽고 이해한 바를 정리하는 목적으로 작성된 글입니다.

[번역] useEffect 완벽가이드


모든 랜더링은 고유의 이펙트를 가진다.

useEffect 안에서 이펙트는 어떻게 최신화된 상태를 읽어들일까? count라는 상태가 있을 때 count는 특별한 값이 아닌 특정 컴포넌트 랜더링에 포함되는 상수이다. 만약 어떤 이벤트핸들러가 count를 참조한다면 이벤트핸들러는 그 랜더링에 속한 count상태를 본다. count는 특정 랜더링에 속하는 변수이기 때문이다.

이펙트에서도 마찬가지이다.
각각의 이펙트 함수는 랜더링마다 별도로 존재하며, 해당 이펙트는 매번 랜더링에 속한 count 값을 본다.
따라서 이펙트 함수는 겉으론 같지만 사실 매 랜더링 마다 다른 함수이다. 각각의 이펙트함수는 그 랜더링에 속한 props와 state를 보기 때문이다.

클린업(cleanup)

useEffect(() => {
  ChatAPI.subscribeToFriendStatus(props.id, handleStatusChange);
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.id, handleStatusChange);
  };
});

첫 번째 랜더링에서 prop.id가 10이고, 두 번째 렌더링에서 prop.id가 20이라면 위의 코드는 어떠한 순서로 실행될까??
아마 아래처럼 흐른다고 생각하기 쉬울 것이다.

  1. 리액트가 {id: 10} 을 다루는 이펙트를 클린업한다.
  2. 리액트가 {id: 20} 을 가지고 UI를 랜더링한다.
  3. 리액트가 {id: 20} 으로 이펙트를 실행한다.

그러나 땡땡땡 틀렸다. ❌
리액트는 브라우저가 페인트하고 난 뒤에야 이펙트를 실행한다. 이렇게 하여 대부분의 이펙트가 스크린 업데이트를 가로막지 않기 때문에 앱을 빠르게 만들어준다. 마찬가지로 이펙트의 클린업도 미뤄진다. 이전 이펙트는 새 prop과 함께 리랜더링 되고 난 뒤에 클린업된다.

  1. 리액트가 {id: 20} 을 가지고 UI를 랜더링한다.
  2. 브라우저가 실제 그리기를 한다. 화면 상에서 {id: 20} 이 반영된 UI를 볼 수 있다.
  3. 리액트는 {id: 10} 에 대한 이펙트를 클린업한다.
  4. 리액트가 {id: 20} 에 대한 이펙트를 실행한다

❓어떻게 prop이 {id: 20}으로 바뀌어도 이전 이펙트의 클린업이 여전히 예전값인 {id: 10}을 "보는"걸까?
이전에 언급되었듯이,

컴포넌트의 랜더링 안에 있는 모든함수(이벤트핸들러,이펙트,API 등)는 render가 호출될 때 정의된 props와 state를 잡아둔다.

그어떠한 것도 첫 번째 랜더링의 클린업이 바라보는 값을 {id: 10} 이외의 것으로 만들 수 없다.

의존성 배열 deps

컴포넌트에 있는 모든 값 중, 이펙트에서 사용될 값은 반드시 deps에 넣어줘야한다. props, state, 함수 등 컴포넌트 안에 있는 모든 것들을 말이다!

매초마다 숫자가 올라가는 카운터를 작성한다고 가정해보자. 우리는 이렇게 생각할 수 있다.
"인터벌을 한 번만 설정하고, 한번만 제거하자. useEffect를 쓸꺼니까, deps에 []를 넣어서 랜더링될때 처음 한번만 실행시키고 제거하면 되겠다!"

위의 로직을 코드로 옮기면 아래와 같다.

function Counter() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(id);
  }, []);
  return <h1>{count}</h1>;
}

그러나 위의 예제는 숫자가 오직 한 번만 증가한다.
❓이건 인터벌이니까 한 번만 실행하면 되는거 아닌가??? 의존성배열은 이펙트가 실행되는 순간을 지정할때 쓰이는거잖아!
❗️의존성 배열은 리액트에게 어떤 이펙트에 쓰이는 것 전부를 알려주는 값이라고 인식해야한다. 이펙트에서 count를 사용하면서도 deps를 []라고 거짓말을 한 것이다..! 거짓말을 하지말자.

첫 번째 랜더링에서 count는 0이다. 따라서 첫 번째 랜더링의 이펙트에서 setCount(count+1)setCount(0+1)이라는 뜻이 된다. deps를 []라고 정의했기 때문에 이펙트를 절대 다시 실행하지 않고, 결국 그로인해 매 초마다 setCount(0+1)을 호출하는 것이다.
이러한 이슈는 해결책을 떠올리기 어렵다. 따라서 언제나 이펙트에 의존성을 솔직하게 전부 명시하는 것을 권장한다.

'개인공부 > 궁금해서 찾아보는' 카테고리의 다른 글

React-router-dom v6 바뀐점 정리하기  (0) 2022.01.04
useEffect 완벽가이드 정독하기1  (0) 2021.06.18
Git과 git reset  (0) 2021.06.14