React, Next, Redux/🚀 React with Hooks

useLayoutEffect로 동기적으로 화면을 업데이트해보자!

DarrenKwonDev 2020. 10. 1. 16:04

useLayoutEffect가 뭐지?

 

useLayoutEffect를 다른 분의 코드에서 보게 되어 무엇인지 알아보기로 했다.

 

import React, { useEffect, useLayoutEffect } from 'react';

function App() {

    useLayoutEffect(() => {
        console.log('useLayoutEffect')
    })

    useEffect(() => {
        console.log('useEffect')
    })

    console.log('render')

    return (
        <>
            render!
        </>
    );
}

export default App;

 

위 컴포넌트를 렌더하면 다음과 같이 차례대로 출력된다.

렌더 => useLayoutEffect => useEffect 순서대로 실행된다는 것이다.

 

 

그러나 이러한 실행 순서보다 더 중요한 것이 있는데, 

useEffect는 일단 화면을 보여주고 변화를 주는 반면에 useLayoutEffect는 변화를 적용 시킨 후 화면을 보여줍니다.

이는 useLayoutEffect가 적용되기 전까지를 visually하게 보여주지 않기 때문에 (blocking) 특유의 flickering (깜빡거림)을 방지할 수 있습니다. 문제는 이 작업이 오래 걸리면 유저는 아예 빈 화면을 보게 된다는 문제점이 있다는 거죠.

 

import React, { useState, useEffect, useLayoutEffect } from 'react';
function App() {
    const [number, setNumber] = useState(0);

    // 버튼을 누르면 0이 살짝 보였다가 10을 보여줌.
    useEffect(() => {
        if (number === 0) {
            setNumber(10)
        }
    }, [number])

    // 버튼을 눌러도 10을 유지함. 변화를 적용한 후 보여주기 때문
    useLayoutEffect(() => {
        if (number === 0) {
            setNumber(10)
        }
    }, [number])


    return (
        <>
            {number}
            <button onClick={() => {
                setNumber(0)
            }}>Button</button>
        </>
    );
}
export default App;

 

useEffect보다 useLayoutEffect를 사용해야하는 때는 DOM을 직접 조작해서 무언가를 보여줄 때, 즉, 훅의 이름대로 레이아웃과 관련된 변화를 감지할 때 사용하면 좋습니다. 기능상으로는 useEffect와 비슷하지만 변화가 적용된 후의 값을 보여주기 때문에 유저 경험에 좋다는 장점이 있습니다.

 

 

정리하자면, useEffect는 비동기적으로, useLayoutEffect는 동기적으로 일어납니다.

 

useEffect runs asynchronously and after a render is painted to the screen.

useLayoutEffect, on the other hand, runs synchronously after a render but before the screen is updated. That goes:

 

따라서 Data Fetching과 같은 작업을 할 때는 데이터를 비동기적으로 받아오는 것이 유저의 경험에 좋습니다. flickering이 일어나도 우선 빠르게 레이아웃을 먼저 보여준 다음 이후 데이터를 주입하는 방식으로 작동하는 것이 일반적이기 때문이죠. 그 밖에 이벤트 핸들러를 붙여준다던지, 모달창을 열고 닫으면서 state를 조작할 때라던지... 이런 경우에 사용합니다.

 

Most of the time your effect will be synchronizing some bit of state or props with something that doesn’t need to happen IMMEDIATELY or that doesn’t affect the page visually.

 

Like if you’re fetching data. that’s not going to result in an immediate change.

Or if you’re setting up an event handler.

Or if you’re resetting some state when a modal dialog appears or disappears.

 

 

 

모든 useEffect를 useLayoutEffect로 교체해야 하는가?

 

Most of the time, useEffect is the right choice. If your code is causing flickering, switch to useLayoutEffect and see if that helps. Because useLayoutEffect is synchronous (a.k.a. blocking) the app won’t visually update until your effect finishes running… it could cause performance issues like stuttering if you have slow code in your effect. Coupled with the fact that most effects don’t need the world to pause while they run, regular useEffect is almost always the one to use.

 

또, 보통 오래 걸리는 작업을 useEffect에서 처리할 때는 Loader를 돌려주었습니다만, Loader로 처리하기 애매한 부분에 useLayoutEffect를 사용해주시면 되겠습니다. 조금 미묘한 부분이기 때문에 이 훅을 아예 사용하지 않으시는 분도 계십니다. 우선은 useEffect로 적용해보고, 특정한 부분을 useLayoutEffect로 교체하는 방식으로 사용해봄직합니다.

 

 

다른 해외 블로그에서는 다음과 같이 정리하더군요.

 

  • useLayoutEffect: If you need to mutate the DOM and/or do need to perform measurements
  • useEffect: If you don't need to interact with the DOM at all or your DOM changes are unobservable (seriously, most of the time you should use this).

 

실전에서 사용해보기

 

useLayoutEffect와 useEffect를 활용하여 의도대로 처리할 수 있습니다.

blur가 먼저 되고 경고창이 뜨도록 하기 위해서 blur는 layoutEffect에 적용하였고, 그 외는 useEffect에 처리하였습니다.

이렇게 하지 않으면 아무 것도 적용되지 않고 alert이 먼저 뜬 후 blur가 적용되어 의도대로 동작하지 않습니다.

useLayoutEffect(() => {
  if (user.isDeleted) {
    wholeWrapperRef.current.style.webkitFilter = "blur(1rem)";
  }
}, []);
 useEffect(() => {
  if (user.isDeleted) {
    window.alert("탈퇴한 유저의 갤러리입니다");
    router.back();
  }
}, []);

 

 

 

 

 

참고한 글)

 

 

[TS]useRef 자세히 알아보기(Typescript, useLayoutEffect)

바닐라 자바스크립트로 DOM 컨트롤을 할 때는 getElementById나 querySelector 등을 이용하여 DOM의 속성을 변경하거나 추가하거나 제거를 해왔습니다. 하지만 리액트에서는 조금 다릅니다. 물론 저 방법�

medium.com

 

When to useLayoutEffect Instead of useEffect (example)

How is useEffect different from useLayoutEffect, and when do you use useLayoutEffect? Most of the time, you want useEffect. Read on for why.

daveceddia.com

 

useEffect vs useLayoutEffect

The simple rules for when to use each.

kentcdodds.com