useLayoutEffect + useEffect, useTransition, useDeferredValue

 

 

 

➡️useLayoutEffect

useLayoutEffect는 DOM 업데이트가 화면에 반영되기 전에 동기적으로 실행된다. 이는 DOM 조작이 필요하거나, 레이아웃 측정 작업을 수행해야 할 때 유용한 훅이다. 

 

useLayoutEffect가 "동기적으로 수행된다."의 의미
useLayoutEffect가 동기적으로 수행된다는 것은, 이 Hook의 콜백 함수가 브라우저가 화면을 다시 그리기 전에 즉시 실행된다는 것을 의미한다. 즉, 렌더링 후, 실제로 화면에 변경 사항이 반영되기 전에 실행된다.

* 동기적 실행
- 이는 브라우저가 다음 작업을 수행하기 전에 현재 작업을 완료해야 함을 의미한다. 따라서 useLayoutEffect 콜백은 브라우저가 화면을 업데이트하기 전에 실행된다.

* 렌더링 사이클
- 일반적으로 React는 컴포넌트를 렌더링하고 나서 브라우저가 DOM을 업데이트한다. 이를 통해 DOM이 완전히 업데이트되기 전에 필요한 작업을 수행할 수 있다.

왜 "레이아웃 측정 작업이 필요한 경우"에 유용한가?
레이아웃 측정 작업은 DOM 요소의 크기, 위치, 스타일 등을 계산하는 작업을 말합니다. 이러한 작업이 필요한 이유는 다음과 같다.

- 정확한 레이아웃 계산: 어떤 경우에는 DOM 요소의 크기나 위치를 정확히 알고 있어야 다른 작업을 수행할 수 있다. 예를 들어, 모달 창을 화면 중앙에 배치하려면 모달 창의 너비와 높이를 알아야 한다.

- 동적 스타일링: DOM 요소의 크기나 위치에 따라 스타일을 동적으로 변경해야 할 때가 있다. 예를 들어, 특정 요소가 화면의 특정 부분에 위치할 때만 특정 스타일을 적용하고자 할 때 유용하다.

- 애니메이션: 애니메이션을 시작하기 전에 요소의 초기 상태를 정확히 파악해야 하는 경우가 있다. 예를 들어, 요소의 크기나 위치를 기준으로 애니메이션을 설정하려면 초기 상태를 정확히 알아야 한다.

 

`useEffect`와 `useLayoutEffect`와 비교하면서 이해해 보자.

 

* 공통점

- 둘 다 리액트 훅(Hook)이고, 함수형 컴포넌트에서 사용한다.

- 컴포넌트가 렌더링 될 때, 특정 작업(사이트 이펙트)을 수행할 수 있다.

- 작업이 완료되었을 때 정리(clean-up) 작업을 할 수 있다.

 

* 차이점

1. 실행 시점

- useEffect : 컴포넌트가 렌더링 된 후 실행된다. 브라우저가 화면을 그린 후에 작업이 수행되기 때문에 화면에 변화가 생길 때마다 업데이트된다.

 

- useLayoutEffect : 컴포넌트가 렌더링된 직후 브라우저가 화면을 그리기 전에 실행된다. 따라서 화면이 깜박이거나 운하는 대로 업데이트되지 않는 상황을 방지할 수 있다.

 

2. 사용되는 상황

- useEffect : 주로 비동기 작업, 데이터 fetching, 이벤트 리스너 설정/해제 등과 같은 작업에 사용된다.

 

-useLayoutEffect : DOM 요소의 크기나 위치를 조정하거나 측정해야 할 때 사용된다. 이는 화면이 업데이트되기 전에 작업이 완료되기 때문에 깜박임 없이 정확한 값을 얻을 수 있다.

 


 

🤔 useLayoutEffect 훅 안에 비동기 통신 함수를 넣으면 화면 렌더링이 끝나기 전에 데이터를 담은 UI가 보일 수 있을까?

라는 생각을 해봤다. useLayoutEffect 내부에 비동기 통신 함수를 넣으면, 화면 렌더링이 끝나기 전에 데이터를 담은 UI가 보여질 가능성은 있다. 하지만 비동기 통신 자체는 여전히 비동기적으로 처리되기 때문에 useLayoutEffect가 주로 동기적으로 실행되는 부분과 조금 다른 관점에 이해해야 한다.

 

useLayoutEffect와 비동기 통신
useLayoutEffect는 렌더링 후, 브라우저가 실제로 화면을 업데이트하기 전에 동기적으로 실행된다. 이는 화면이 사용자에게 그려지기 전에 DOM 조작을 할 수 있게 해 준다. 하지만 비동기 함수(예: fetch 함수)는 여전히 비동기적으로 동작하기 때문에, 비동기 통신이 완료될 때까지 기다리지는 않다.

 

➡️예시

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

function ExampleComponent() {
  const [data, setData] = useState(null);

  useLayoutEffect(() => {
    let isMounted = true;

    // 비동기 함수 호출
    const fetchData = async () => {
      const response = await fetch('https://api.example.com/data');
      const result = await response.json();
      if (isMounted) {
        setData(result);
      }
    };

    fetchData();

    return () => {
      isMounted = false;
    };
  }, []); // 빈 배열을 의존성으로 하여 컴포넌트가 마운트될 때 한 번만 실행

  return (
    <div>
      <h1>데이터 패칭 예제</h1>
      {data ? <pre>{JSON.stringify(data, null, 2)}</pre> : <p>로딩 중...</p>}
    </div>
  );
}

export default ExampleComponent;

 

* 비동기 통신은 여전히 비동기적:
- useLayoutEffect 내에서 비동기 통신을 수행하더라도, 비동기 통신 자체는 비동기적으로 동작한다. 따라서 데이터를 받아오기 전에는 초기 상태가 렌더링 된다.

 

*사용자 경험:
- useLayoutEffect를 사용하여 비동기 통신을 수행할 경우, 데이터가 로드되기 전까지 화면이 빈 상태로 보일 수 있다. 이는 사용자 경험에 부정적인 영향을 줄 수 있다.

 

* 렌더링 블로킹:
- useLayoutEffect 내부에서 동기적으로 실행되는 코드가 많아지면, 초기 렌더링이 블로킹될 수 있다. 이는 성능 문제를 일으킬 수 있다.

 

* 결론 ⭐
useLayoutEffect를 사용하여 비동기 통신을 수행할 수 있지만, 비동기 통신 자체는 여전히 비동기적으로 동작한다. 따라서 데이터를 받아오기 전까지는 빈 상태가 렌더링된다. 일반적으로 데이터 패칭과 같은 비동기 작업은 useEffect를 사용하는 것이 더 적절하다. useLayoutEffect는 주로 DOM 요소의 크기나 위치를 측정하거나, 동기적으로 스타일을 조정할 때 사용되는 것이 좋다.

 

 

➡️사용 예시

export default function NewHook() {
  const [name, setName] = useState('');

  useEffect(() => {
    setName('박설민');
  }, []);

  console.log('render', name);

  return (
    <div>
      <div>안녕하세요. {name}입니다.</div>
      <div>안녕하세요. {name}입니다.</div>
      <div>안녕하세요. {name}입니다.</div>
      <div>안녕하세요. {name}입니다.</div>
      <div>안녕하세요. {name}입니다.</div>
      <div>안녕하세요. {name}입니다.</div>
      <div>안녕하세요. {name}입니다.</div>
    </div>
  );
}

 

위의 예시는 화면 UI 렌더링이 완료된 후 useEffect가 실행되어 name이 표시된다.

짧은 순간이지만 화면이 최초 렌더링이 된 상태는 name이 비어있는 상황이라 표시되어 있지 않고, 이후 useEffect가 실행되어 짧게 깜박이고 name에 값이 들어가 화면에 표시된다.

 

이러한 사용자 불편함을 해결하기 위해 useLayoutEffect를 사용하여 UI렌더링이 끝나기 전에 useLayoutEffect가 실행되어 값이 할당되고 화면에 표시되어 깜박임 현상을 없앨 수 있다.

 


 

➡️useTransition 

useTransition은 상태 업데이트가 성능에 영향을 미칠 수 있는 경우, 상태 업데이트를 비동기적으로 처리하여 사용자 인터페이스의 응답성을 향상시킬 수 있다. useTransition 은 두 값을 반환한다. isPending 상태와 startTransition 함수이다.

 

예를 들어, 입력창에 작성한 값이 아래에 100개의 리스트로 작성되는 경우 입력창은 사용자가 바로 작성 및 수정할 수 있게 빠르게 상태 변화가 필요하지만, 아래의 리스트는 조금 천천히 렌더링 되어 화면에 표시되어도 괜찮다. 그 예가 추천 검색어 기능이다. 

 

export default function NewHook() {
  const [text, setText] = useState('');
  const [list, setList] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleChange = (event) => {
    setText(event.target.value);
    startTransition(() => {
      const newList = Array(100).fill(event.target.value);
      setList(newList);
    });
  };

  return (
    <div>
      <input type='text' value={text} onChange={handleChange} />
      {isPending ? <p>로딩 중...</p> : null}
      <ul>
        {list.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

 

이 예시에서는 startTransition를 사용하여 대량의 리스트 업데이트를 비동기적으로 처리하여 입력 필드의 응답성을 유지할 수 있다.

 

➡️useDeferredValue

useDeferredValue와 useTransition은 둘 다 성능 최적화와 사용자 경험 향상을 위해 사용된다. 비슷한 목적을 가지고 있지만, 사용되는 상황과 동작 방식에서 차이가 있다. 

 

두 훅을 비교하면서 이해해 보자...

 

특성 useTransition useDeferredValue
목적 상태 업데이트를 트랜지션으로 처리하여 낮은 우선순위로 설정 값의 업데이트를 지연시켜 성능 최적화
사용 상황 상태 업데이트가 사용자의 다른 중요한 작업을 방해하지 않도록 할 때 값이 자주 변경되며, 즉시 반영될 필요가 없을 때
반환 값 [isPending, startTransition] deferredValue
주요 기능 트랜지션 상태 업데이트 시작, 트랜지션 진행 중 여부 확인 지연된 값 반

 

"트랜지션으로 처리하여 낮은 우선순위로 설정한다."는 말은 다소 어렵다.

🔍기본 개념
- 상태 업데이트 : React에서 상태가 업데이트되면 화면이 다시 그려진다.(리렌더링)
- 우선순위 : 어떤 작업을 먼저 할지 결정하는 것. 중요한 작업은 높은 우선순위, 덜 중요한 작업은 낮은 우선순위를 가진다.

⚡트랜지션의 의미
- 트랜지션 : 여기서는 "상태 업데이트를 서서히 처리한다"라고 생각하면 된다. 즉, 중요한 작업이 먼저 처리될 수 있도록 상태 업데이트를 뒤로 미루는 것이다.

✍️예시
(일반적인 상태 업데이트)
1. 사용자가 입력 필드에 글자를 입력한다.
2. 입력할 때마다 상태가 즉시 업데이트된다.
3. 즉시 업데이트되기 때문에 화면이 바로바로 리렌더링 된다.

(트랜지션을 사용한 상태 업데이트)
1. 사용자가 입력 필드에 글자를 입력한다.
2. useTransition을 사용하여 상태 업데이트를 트랜지션으로 처리한다.
3. 트랜지션으로 처리된 상태 업데이트는 즉시 반영되지 않고, 사용자 입력 같은 더 중요한 작업이 먼저 처리된다.
4. 중요한 작업이 끝난 후에 상태 업데이트가 처리되어 화면이 리렌더링 된다.

다시 정리하면...

- 즉시 업데이트 : 사용자의 모든 입력이 즉시 반영되어 화면이 바로바로 리렌더링 된다.

- 트랜지션 업데이트 : 사용자의 입력이 바로 반영되지 않고, 중요한 작업(예:입력 반응)이 먼저 처리된 후에 업데이트된다.

 

➡️ useDeferredValue

useDeferredValue는 주어진 값의 업데이트를 지연시켜, 해당 값이 즉시 반영되지 않도록 한다. 이렇게 하면, 값이 자주 변경될 때 성능을 최적화할 수 있다.

 

- 지연된 값 : useDeferredValue는 주어진 값을 지연된 상태로 반환한다. 이 값은 즉시 업데이트되지 않고, React가 더 중요한 작업을 먼저 처리한 후 업데이되도로 한다.

 

- 사용 시점 : 값이 즉시 업데이트될 필요는 없지만, 최신 상태를 유지해야 할 때 사용한다.

 


 

 

 

 

⭐요약

 

➡️useTransition : 사용자 인터페이스에서 중요한 작업(예: 사용자 입력 반응)이 먼저 처리되고, 덜 중요한 작업(예: 데이터 필터링)이 나중에 처리되어야 할 때 사용한다.

 

➡️useDeferredValue : 값의 업데이트가 바로 반영될 필요는 없지만! 해당 값이 다른 곳에서 사용될 때 최신 상태여야 하는 경우 사용한다. 주로 큰 데이터 세트의 필터링 작업에 적합하다.