이 글은 아래의 글을 읽고 이해한 바를 정리하는 목적으로 작성된 글입니다.
모든 랜더링은 고유의 이펙트를 가진다.
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이라면 위의 코드는 어떠한 순서로 실행될까??
아마 아래처럼 흐른다고 생각하기 쉬울 것이다.
- 리액트가 {id: 10} 을 다루는 이펙트를 클린업한다.
- 리액트가 {id: 20} 을 가지고 UI를 랜더링한다.
- 리액트가 {id: 20} 으로 이펙트를 실행한다.
그러나 땡땡땡 틀렸다. ❌
리액트는 브라우저가 페인트하고 난 뒤에야 이펙트를 실행한다. 이렇게 하여 대부분의 이펙트가 스크린 업데이트를 가로막지 않기 때문에 앱을 빠르게 만들어준다. 마찬가지로 이펙트의 클린업도 미뤄진다. 이전 이펙트는 새 prop과 함께 리랜더링 되고 난 뒤에 클린업된다.
- 리액트가 {id: 20} 을 가지고 UI를 랜더링한다.
- 브라우저가 실제 그리기를 한다. 화면 상에서 {id: 20} 이 반영된 UI를 볼 수 있다.
- 리액트는 {id: 10} 에 대한 이펙트를 클린업한다.
- 리액트가 {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 |