본문으로 바로가기

 

 

이번 글에서는 왜 eslint-disable-line, react-hooks/exhaustive-deps 경고를 알려주는지 알아보도록 하겠습니다.

리액트 코드를 작성하거나, 읽다보면 제법 심심치 않게 eslint-disable-line, react-hooks/exhaustive-deps 주석을 사용해 아래의 경고를 무시한는 것을 볼 수 있습니다. (저 또한.. 그랬습니다..) 이 ESLint룰은 useEffect 인수 내부에서 사용하는 값 중 의존성 배열에 포함돼 있지 않은 값이 있을 때 경고를 발생시킵니다.

 

정말로 필요할 때에는 사용할 수도 있지만 대부분의 경우에는 의도치 못한 버그를 만들 가능성이 큰 코드입니다. 이 코드를 사용하는 대부분의 예제가 빈 배열 []을 의존성으로 할 때, 즉 컴포넌트를 마운트하는 시점에만 무언가를 하고 싶다라는 의도로 작성하곤 합니다. 그러나 이는 클래스 컴포넌트트의 생명중기 메서드인 *componentDidMount에 기반한 접근법으로, 가급적이면 사용하면 안됩니다.

 

useEffect는 반드시 의존성 배열로 전달한 값의 변경에 의해 실행돼야 하는 훅입니다. 그러나 의존성 배열을 넘기지 않은 채 콜백 함수 내부에서 특정 값을 사용한다는 것은, 이 부수 효과가 실제로 관찰해서 실행돼야 하는 값과는 별개로 작동한다는 것을 의미합니다. 즉, 컴포넌트의 state, props와 같은 어떤 값의 변경과 useEffect의 부수 효과가 별개로 작동하게 된다는 것입니다.

 

따라서 정말로 의존성으로 빈 배열인 []가 필요하다면 최초에 함수 컴포넌트가 마운트됐을 시점에만 콜백 함수 실행이 필요한지를 다시 한 번 되물어봐야 합니다. 만약 정말 '그렇다'라고 하면 useEffect 내 부수 효과가 실행될 위치가 잘못됐을 가능성이 큽니다. 아래 코드를 살펴보겠습니다.

function Component({ log }: { log: string }) {
  useEffect(() => {
    logging(log);
  }, []) // eslint-disable-line, react-hooks/exhaustive-deps 
}

 

위 코드는 log가 최초로 props로 넘어와서 컴포넌트가 최초로 렌더링된 시점에만 실행됩니다. 코드를 작성한 의도는 아마도 해당 컴포넌트가 최초로 렌더링됐을 때만 logging()을 실행하고 싶어서일 것입니다.

 

그러나 위 코드는 당장은 문제가 없을지라도 버그의 위험성을 안고 있습니다. log가 아무리 변하더라도 useEffect의 부수 효과는 실행되지 않고, useEffect의 흐름과 컴포넌트의 props.log의 흐름이 맞지 않게 됩니다.

 

따라서 앞에서 logging이라는 작업은 log를 props로 전달하는 부모 컴포넌트에서 실행되는 것이 옳을지도 모릅니다. 부모 컴포넌트에서 Componenet가 렌더링되는 시점을 결정하고 이에 맞게 log 값을 넘겨준다면 useEffect의 해당 주석을 제거해도 위 예제 코드와 동일한 결과를 만들 수 있고 Component의 부수 효과 흐름을 거스르지 않을 수 있기 때문입니다.

 

useEffect에 빈 배열을 넘기기 전에는 정말로 useEffect의 부수 효과가 컴포넌트 상태와 별개로 작동해야만 하는지, 혹은 여기서 호출하는 게 최선인지 한 번 더 컴토해봐야 합니다.

 

빈 배열이 아닐 때도 마찬가지입니다. 만약 특정 값을 사용하지만 해당 값의 변경 시점을 피할 목적이라면 메모이제이션을 적절히 활용해 해당 값의 변화를 막거나 적당한 실행 위치를 다시 한 번 고민해보는 것이 좋습니다.

 


* componentDidMount

  • componentDidMount 메서드는 클래스 컴포넌트의 생명주기 메서드 중 하나로, 컴포넌트가 DOM에 마운트된 직후에 호출됩니다.
  • 주로 네트워크 요청, 초기 데이터 가져외 등 useEffect와 사용 용도가 비슷합니다.
  • 이 메서드 내에서 this.setState()를 호출하여 컴포넌트 상태를 변경할 수 있습니다.
  • 클래스 컴포넌트에서만 사용 가능합니다.
class MyComponent extends React.Component {
  componentDidMount() {
    // 컴포넌트가 마운트된 후 실행될 코드
  }
}

 

useEffect와의 차이점으로는 아래와 같습니다.

  • componentDidMount는 클래스 컴포넌트에서만 사용 가능하고, useEffect는 함수형 컴포넌트에서 사용합니다.
  • useEffect는 컴포넌트 렌더링과 부수 효과를 더 명확하게 분리하고 상태와 프롭스를 조절할 수 있는 장점이 있습니다.
  • 클래스 컴포넌트와 함수형 컴포넌트 모두에서 비동기 작업을 처리하는 방법을 제공하며, 선택은 프로젝트 요구사항과 선호에 따라 결정됩니다.

 

 

References

모던 리액트 딥다이브

2022.03.25 - [React] useEffect 사용법 및 예제