useEffect와 useLayoutEffect: effect 바르게 쓰기

May 24, 2022

useEffect? useLayoutEffect?

공식문서를 읽어보자.

useEffect - Effect Hook을 사용하면 함수 컴포넌트에서 side effect를 수행할 수 있습니다.

useLayoutEffect - 이 함수의 시그니처는 useEffect와 동일하긴 한데…

side effect란 함수가 받은 인자 외에 외부에서 함수의 출력에 영향을 주는 모든 상태 변경을 의미한다. 예를 들어 함수의 내부에서 Math.random(), Date.now()를 사용하여 실행할 때마다 결과가 달라지는 것이 대표적인 side effect이다. 그 외에도 함수 내부에서 인자로 받지 않은 외부의 상태를 참조하거나 API로 서버의 데이터를 가져오는 등의 작업도 side effect에 포함된다.

useEffect와 useLayoutEffect 모두 컴포넌트에서 side effect가 있는 작업을 수행할 때 사용할 수 있다.

차이점

useEffect와 useLayoutEffect의 차이는 실행 시점과 동작 방식(동기/비동기)에 있다. useEffect 훅은 컴포넌트 렌더링과 브라우저 페인팅이 끝난 후 비동기적으로 실행된다. 반면 useLayoutEffect 훅은 컴포넌트 렌더링 후 동기적으로 실행되어 작업이 완료되면 브라우저 페인팅이 시작된다. useLayoutEffect의 실행 시점과 동작 방식은 componentDidMountcomponentDidUpdate의 스케쥴링과 동일하다.

hook flow 비교

hook-flow
hook flow

useEffect

  1. 리액트 앱에서 렌더링이 트리거 됨 (dependency 수정됨)
  2. 리액트가 컴포넌트를 렌더링함
  3. 브라우저가 화면을 새로 그림
  4. useEffect가 백그라운드에서 실행됨
  5. useEffect 안에서 DOM 노드 ref를 조작함
  6. 리렌더링, 이펙트가 DOM을 수정하는 동안 잠시 화면이 빔.
  7. 브라우저가 화면을 새로 그림

useLayoutEffect

  1. 리액트 앱에서 렌더링이 트리거 됨 (dependency 수정됨)
  2. 리액트가 컴포넌트를 렌더링함
  3. useLayoutEffect가 실행됨
  4. useLayoutEffect가 끝나기를 기다림
  5. 브라우저가 화면을 새로 그림

useLayoutEffect, 언제 써야 하나

브라우저 페인트 전 DOM 조작, 혹은 레이아웃 정보를 읽어야 할 때

  1. 스크롤 위치 확인
  2. 화면이 깜빡이는 상황
  3. 그 외: ref가 업데이트 되기 전 이전 값 확인
const ref = useRef()

useEffect(() => {
  ref.current = "some value"
})

// 다른 hook 등에서 다음 실행
useLayoutEffect(() => {
  console.log(ref.current) // <-- 이전 값 로그
})

생각해보기

데이터 fetching은 어디서 하는게 좋을까?

useLayoutEffect가 useEffect 전에 실행된다고..? 그럼 useLayoutEffect에서 데이터 fetching을 하면 더 빨리 받겠네?? 🤔

review
아...

문제는 useLayoutEffect가 동기적이라는 것이다. useLayoutEffect가 실행되는 동안 브라우저 페인팅을 블로킹하면서 웹앱이 일시중지된다. 이펙트 내부에 느린 작업이 있는 경우 버벅임과 같은 성능 이슈를 발생시킬 수 있다.

즉, 더 빠른 데이터 fetching에서 얻는 이점보다 브라우저 페인팅이 블로킹되면서 생기는 성능 이슈가 더 심각하다.

useLayoutEffect와 SSR

Warning: useLayoutEffect does nothing on the server because its effect cannot be encoded into the server renderer’s output format…

When dealing with SSR, both useEffect & useLayoutEffect won’t work unless that JavaScript has been properly loaded. Therefore, there’s the above warning we might see in the console. The reason why it doesn’t generate with useEffect is, it is not concerned with the render cycle of the component unlike useLayoutEffect is concerned and cares what users would see on the very first render of the component.

React Community suggests two ways to fix this,

1. Of course, the first thing we can do is to try to convert it to a useEffect hook if possible.

2. If there is a flickering issue with the useEffect (as we discussed earlier ) and developers do need useLayoutEffect, then another way could be delaying that very component that uses the hook until the JavaScript has been loaded. Another way of saying, lazily load the React Component.


우정민

웹 개발, 프론트엔드