useLayoutEffect
[리액트 공식문서]
- 방향성은 useEffect와 동일하지만, 모든 DOM요소의 변경 후에 동기적으로 발생한다.(useEffect는 비동기)
- DOM에서 레이아웃을 읽고 동기적으로 리렌더링하는 경우에 사용해야한다.
- useLayoutEffect의 내부에 예약된 코드들은 브라우저가 화면을 그리기 이전에 동기적으로 수행된다.
[Kent C. Dodds | Remix]
기본 동작은 React가 코드를 실행하기 전에 브라우저가 DOM를 업데이트하도록 하는 것이다. 이것은 코드가 브라우저를 차단하지 않고 사용자가 DOM을 더 빨리 볼수 있도록 한다. 그래서 대부분 useEffect를 사용한다.
[stack overflow]
useEffect와 어느 정도 비슷하지만 React가 DOM에 업데이트를 커밋한 후에 실행된다는 점에서 다르다.
업데이트 후 요소 간 거리를 계산하거나, 업데이트 후 side-effect를 수행해야 하는 경우 등 드문 상황(rare cases)에 사용된다.
Hook Flow
useEffect와 useLayoutEffect의 차이는 Browser paints screen 전후로 실행되는 순서라고 볼 수있다.
useEffect와 useLayoutEffect의 비교
useEffect
먼저 useEffect는 비동기적으로 실행된다.
위에서부터 아래로 코드가 있을 경우에 위의 코드의 결과값을 받기 전에 아래의 코드를 수행할 수 있다는 의미이다. 또한 컴포넌트들이 render 되고 해당 컴포넌트가 paint되면 그때 useEffect가 실행된다.
간단하게 브라우저 렌더링 과정을 살펴보면 HTML을 파싱하여 DOM Tree를 구성하고 CSS로 CSSOM Tree를 생성한다. 이 두개의 Tree 정보를 이용하여 실제로 브라우저의 화면에 노출되어야 하는 노드들에 대한 정보 Render Tree를 생성한다. 그 후에 reflow와 repaint 단계를 수행한다.
- reflow:
생성된 DOM 노드의 레이아웃 수치(너비, 높이, 위치 등) 변경 시 영향 받은 모든 노드의(부모, 자식 등) 수치를 다시 계산하여 렌더 트리를 재생성하는 과정 - repaint:
reflow 과정이 끝난 후 재생성된 렌더 트리를 다시 그리는 과정
보통 DOM을 조작할때마다 reflow와 repaint를 하기때문에 브라우저에서 성능 저하가 발생한다.
virtual DOM을 사용하는 React의 경우에는 reflow와 repaint를 최소화 한다는 장점을 갖고 있다.
결론은 useEffect는 paint 후에 실행되기 때문에 DOM에 영향을 미치는 코드가 있다면 다시 그리므로 화면 깜빡임이 발생한다.
useLayoutEffect
useLayoutEffect는 동기적으로 동작하며 paint하기 전에 실행된다. 화면에 그리기 전에 수행되므로 useLayoutEffect에 DOM에 영향을 미치는 코드를 넣어도 화면 깜빡임이 발생하지 않는다.
렌더링 이전에 하고 싶은 동작이나 렌더링할 상태가 Effect 내에서 초기화 하는 경우에 활용할 수 있다.
하지만 동기적인 동작에서 발생하는 단점을 가지고 있다.
만약에 위의 코드가 데이터를 불러오는 부분인데 10초가 걸린다고 가정하면 유저는 10초동안 빈 화면을 보게 되는 것이다.
많은 개발자들이 언급 했던 것처럼 useEffect를 사용하여 설계하다가 깜빡임에 문제가 있다면 useLayoutEffect를 사용하는 것이 맞는것같다.
SSR에서 사용하고 싶은 경우
useLayoutEffect는 CSR에서만 사용할 것을 권장하는데 SSR에서도 사용하고 싶은 경우가 있을 것이다.
import { useEffect, useLayoutEffect } from 'react'
export const useIsomorphicEffect = () => {
return typeof window !== 'undefined' ? useLayoutEffect : useEffect
}
렌더링 되어 window객체의 생성여부에 따라 useEffect를 사용할지 useLayoutEffect를 사용할지 결정하는 커스텀 훅을 만들어 사용하면 된다.
import React, { useEffect, useLayoutEffect } from 'react'
import { useDiffEffect } from './useDiffEffect'
const App = () => {
const diffEffect = useDiffEffect()
diffEffect(() => {
// code
}, [])
return (
<div>
Hellow world
</div>
)
}