본문 바로가기
Tech/React

useEffect 정리

by egas 2021. 7. 22.

Effect Hook을 사용하면 컴포넌트에서 side effect를 수행할 수 있다. 데이터 가져오기, 구독(subscription) 설정하기, 수동으로 리액트 컴포넌트의 DOM을 수정하는 것까지 이 모든 것이 side effects다.

 

정리(Clean-up)를 이용하지 않는 Effects

 

리액트가 DOM을 업데이트한 뒤 추가로 코드를 실행해야 하는 경우가 있다. 네트워크 리퀘스트, DOM 수동 조작, 로깅 등은 정리(clean-up)가 필요 없는 경우이다.

 

Class

리액트의 class 컴포넌트에서 render 메서드 그 자체는 side effect를 발생시키지 않는다. DOM을 업데이트하고 난 이후에 effect를 수행한다. 리액트 class에서 side effect를 componentDidMount componentDidUpdate에 두는 것이 바로 이 때문이다.

 

Hook

useEffect가 하는 일은 무엇일까요? useEffect Hook을 이용하여 우리는 리액트에게 컴포넌트가 렌더링 이후에 어떤 일을 수행해야하는 지를 말한다. 리액트는 우리가 넘긴 함수를 기억했다가(이 함수가 ‘effect’다) DOM 업데이트를 수행한 이후에 불러낸다.

 

useEffect를 컴포넌트 안에서 불러내는 이유는 무엇일까요? useEffect를 컴포넌트 내부에 둠으로써 effect를 통해 state 변수(또는 그 어떤 prop에도)에 접근할 수 있다.

 

useEffect는 렌더링 이후에 매번 수행되는 걸까요? 맞다. 기본적으로 첫번째 렌더링 이후의 모든 업데이트에서 수행된다. 리액트는 effect가 수행되는 시점에 이미 DOM이 업데이트되었음을 보장한다.

 

useEffect에 전달된 함수는 모든 렌더링에서 다르다. 리렌더링하는 때마다 모두 이전과 다른 effect로 교체하여 전달한다.

 

componentDidMount 혹은 componentDidUpdate와는 달리 useEffect에서 사용되는 effect는 브라우저가 화면을 업데이트하는 것을 차단하지 않는다. 대부분의 effect는 동기적으로 실행될 필요가 없다. 흔하지는 않지만 (레이아웃의 측정과 같은) 동기적 실행이 필요한 경우에는 useEffect와 동일한 API를 사용하는 useLayoutEffect라는 별도의 Hook이 존재한다.

 

정리(clean-up)를 이용하는 Effects

외부 데이터에 구독(subscription)을 설정해야 하는 경우 메모리 누수가 발생하지 않도록 정리(clean-up)하는 것이 매우 중요하다.

 

Class

리액트 class에서는 흔히 componentDidMount에 구독(subscription)을 설정한 뒤 componentWillUnmount에서 이를 정리(clean-up)한다.

 

Hook

effect에서 함수를 반환하는 이유는 무엇일까요? 이는 effect를 위한 추가적인 정리(clean-up) 메커니즘이다. 모든 effect는 정리를 위한 함수를 반환할 수 있다.

 

리액트가 effect를 정리(clean-up)하는 시점은 정확히 언제일까요? 리액트는 컴포넌트가 마운트 해제되는 때에 정리(clean-up)를 실행한다.

 

Multiple Effect

Hook을 이용하면 생명주기 메서드에 따라서가 아니라 코드가 무엇을 하는지에 따라 나눌 수가 있다. 리액트는 컴포넌트에 사용된 모든 effect를 지정된 순서에 맞춰 적용한다.

 

effect가 업데이트 시마다 실행되는 이유

useEffect는 기본적으로 업데이트를 다루기 때문에 더는 업데이트를 위한 특별한 코드가 필요 없다. 다음의 effect를 적용하기 전에 이전의 effect는 정리(clean-up)한다. 

// { friend: { id: 100 } } state을 사용하여 마운트합니다.
ChatAPI.subscribeToFriendStatus(100, handleStatusChange);     // 첫번째 effect가 작동합니다.

// { friend: { id: 200 } } state로 업데이트합니다.
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // 이전의 effect를 정리(clean-up)합니다.
ChatAPI.subscribeToFriendStatus(200, handleStatusChange);     // 다음 effect가 작동합니다.

// { friend: { id: 300 } } state로 업데이트합니다.
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // 이전의 effect를 정리(clean-up)합니다.
ChatAPI.subscribeToFriendStatus(300, handleStatusChange);     // 다음 effect가 작동합니다.

// 마운트를 해제합니다.
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // 마지막 effect를 정리(clean-up)합니다.

 

Effect를 건너뛰어 성능 최적화하기

모든 렌더링 이후에 effect를 정리(clean-up)하거나 적용하는 것이 때때로 성능 저하를 발생시키는 경우도 있다. 클래스 컴포넌트의 경우에는 componentDidUpdate에서 prevProps prevState와의 비교를 통해 이러한 문제를 해결할 수 있다. 이러한 요구 조건은 흔하기 때문에 useEffect Hook API에 이미 내재하여 있다.

 

특정 값들이 리렌더링 시에 변경되지 않는다면 리액트로 하여금 effect를 건너뛰도록 할 수 있다. useEffect의 선택적 인수인 두 번째 인수로 배열을 넘기면된다. 정리(clean-up)를 사용하는 effect의 경우에도 동일하게 작용한다. (두 번째 인자는 빌드 시 변환에 의해 자동으로 추가될 수 있다.)

 

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // count가 바뀔 때만 effect를 재실행합니다.

 

이 최적화 방법을 사용한다면 배열이 컴포넌트 범위 내에서 바뀌는 값들과 effect에 의해 사용되는 값들을 모두 포함한다. 그렇지 않으면 현재 값이 아닌 이전의 렌더링 때의 값을 참고하게된다. 

 

effect를 실행하고 이를 정리(clean-up)하는 과정을 (마운트와 마운트 해제 시에)딱 한 번씩만 실행하고 싶다면, 빈 배열([])을 두 번째 인수로 넘기면된다. 리액트로 하여금 우리의 effect가 prop나 state의 그 어떤 값에도 의존하지 않으며 따라서 재실행되어야 할 필요가 없음을 알게 한다. (빈 배열([])을 넘기게 되면, effect 안의 prop과 state는 초깃값을 유지하게된다.)

 

 

참고

https://ko.reactjs.org/docs/hooks-effect.html

 

Using the Effect Hook – React

A JavaScript library for building user interfaces

ko.reactjs.org

https://ko.reactjs.org/docs/hooks-reference.html#uselayouteffect

 

728x90

댓글