[React] useTransition & useDeferredValue

Main·2024년 10월 18일
0

React

목록 보기
25/31
post-thumbnail

useTransition

상태 변화의 우선순위를 지정하기 위해 useTransition 훅이 새로 도입되었습니다.
useTransition은 비동기 작업을 처리할 때 사용되며, 특히, 복잡한 상태 업데이트나 UI 렌더링이 필요할 때 해당 작업을 우선순위가 낮은 작업으로 처리하여 사용자 인터페이스가 끊김 없이 반응할 수 있도록 도와줍니다. 이를 통해 성능을 향상시키고, 사용자가 어떤 작업을 했을 때 즉각적인 반응을 볼 수 있게 해줍니다.


useTransition 반환값

  • isPending : 상태 업데이트가 지연 중인지 여부를 나타내는 불리언 값.
  • startTransition : 긴급하지 않은 상태 업데이트를 실행하는 함수.
const [isPending, startTransition] = useTransition();

const handleClick = () => {
  startTransition(() => {
    // 긴급하지 않은 상태 업데이트
    setState(newValue);
  });
};

useTransition의 동작 원리

useTransition의 내부 동작 원리는 React의 동시성 모드(Concurrent Mode)와 관련이 깊습니다. React 18에서 도입된 이 동시성 모드를 통해, React는 상태 업데이트와 렌더링 작업을 우선순위에 따라 관리할 수 있게 되었습니다. useTransition은 이러한 동시성 모드의 기능을 활용하여 상태 업데이트의 우선순위를 조절하고, 긴급하지 않은 작업을 비동기적으로 처리하는 방식으로 성능을 최적화합니다.

1 ) 상태 업데이트 우선순위 분리

useTransition을 사용하면 상태 업데이트 작업을 두 가지 우선순위로 나누어 처리할 수 있습니다.

  • 높은 우선순위 작업 : 예를 들어, 사용자의 입력, 버튼 클릭 등과 같은 즉각적인 반응이 필요한 작업.
  • 낮은 우선순위 작업 : 필터링, 리스트 정렬, 애니메이션 처리 등 긴급하지 않은 작업.

이러한 작업을 우선순위에 따라 분리함으로써 UI가 중요한 작업(예: 입력 처리)을 방해받지 않도록 하고, 덜 중요한 작업은 나중에 처리하게 됩니다.

2 ) startTransition 함수

startTransition은 긴급하지 않은 작업을 감싸서 처리하는 역할을 합니다. 이를 호출하면 React는 해당 상태 업데이트를 낮은 우선순위 작업으로 분류하여, 동시성 모드에서 처리할 수 있게 됩니다. 내부적으로 startTransition은 이 작업을 비동기적으로 스케줄링합니다.


useTransition의 특징

  • 상태 업데이트의 우선순위 조절 : useTransition은 상태 업데이트가 즉시 처리될 필요가 없는 경우, 즉 긴급하지 않은 작업을 낮은 우선순위로 처리합니다. 예를 들어, 대량의 데이터 필터링 작업이나 애니메이션은 우선순위가 낮은 작업으로 처리되어 사용자 상호작용(클릭, 입력 등)과 같은 긴급한 작업에 영향을 미치지 않습니다.
  • 비동기적 처리 : useTransition비동기적으로 상태를 업데이트합니다. 즉, UI에서 즉각적인 반응이 필요한 작업을 먼저 처리한 후, 나머지 상태 업데이트를 나중에 백그라운드에서 처리합니다. 이를 통해 UI의 성능을 유지하면서도 작업을 효율적으로 처리할 수 있습니다.

useTransition의 장점

  • UI 응답성 향상 : useTransition을 사용하면 긴급하지 않은 상태 업데이트가 백그라운드에서 처리되므로, 사용자가 상호작용하는 중요한 작업이 먼저 처리됩니다. 즉, 사용자 입력에 즉각적으로 반응할 수 있어 UI가 끊김 없이 동작합니다.
  • 성능 최적화 : 상태 업데이트가 필요한 작업이 많을 때, 중요한 작업과 덜 중요한 작업을 우선순위에 따라 분리함으로써 전체 애플리케이션의 성능을 최적화할 수 있습니다. 긴급하지 않은 작업이 우선순위가 낮게 처리되어, 더 적은 CPU 자원을 사용하며 중요한 작업이 빠르게 처리됩니다.
  • 사용자 경험 향상 : useTransition은 중요한 작업(예: 클릭, 입력 등)이 차질 없이 처리되도록 해 주어 매끄러운 사용자 경험을 제공합니다. 사용자는 필터링, 대량의 데이터 렌더링 등 무거운 작업 중에도 입력 또는 UI 상호작용의 즉각적인 반응을 경험할 수 있습니다.
  • 로딩 상태 처리 : 트랜지션 작업이 지연되거나 백그라운드에서 처리되는 동안 isPending 값을 이용해 로딩 상태를 처리할 수 있습니다. 이를 통해 사용자는 작업이 진행 중임을 인지하고 기다릴 수 있게 됩니다.
  • 복잡한 UI 관리 가능: UI가 복잡하고 다양한 상태 업데이트가 필요한 경우, useTransition을 사용하여 긴급하지 않은 작업을 백그라운드에서 처리함으로써 복잡한 UI도 부드럽게 렌더링할 수 있습니다.

useTransition의 단점

  • 복잡도 증가: useTransition을 남용하거나 잘못 사용하면 애플리케이션의 복잡도가 증가할 수 있습니다. 상태 업데이트를 어느 시점에 우선순위를 낮출 것인지 결정하는 것은 개발자의 몫이며, 잘못 설정된 우선순위로 인해 기대하지 않은 성능 문제가 발생할 수 있습니다.
  • 여전히 성능 저하 가능성 : 트랜지션 작업이 너무 많거나 복잡한 작업이 과도하게 사용되면, 비동기적 상태 업데이트를 처리하는 중에도 성능 저하가 발생할 수 있습니다. 특히 여러 번의 트랜지션 작업이 동시에 진행되는 경우, 백그라운드 작업이 과부하 상태가 되어 UI가 느려질 수 있습니다.
  • 지연에 따른 사용자 혼란 : 트랜지션으로 인해 상태 업데이트가 지연되면, 사용자가 즉각적으로 결과를 보지 못할 수 있습니다. 이로 인해 사용자는 작업이 완료되지 않았다고 생각하거나, 응답이 늦다는 느낌을 받을 수 있습니다. 이를 방지하려면 적절한 로딩 상태(isPending)를 보여주는 것이 중요합니다.
  • 브라우저 성능에 의존 : useTransition은 브라우저의 성능에 따라 다르게 동작할 수 있습니다. 특히, 브라우저가 리소스가 부족할 경우 낮은 우선순위 작업이 매우 느리게 처리될 수 있어 기대했던 만큼의 성능 향상을 얻지 못할 수도 있습니다.

useTransition 사용 예시

useTransition을 사용해 검색과 같은 작업을 처리할 때, 검색어 입력은 즉시 반영되지만 결과 목록을 업데이트하는 작업은 우선순위를 낮춰서 UI가 끊기지 않게 처리하는 경우입니다.

import { useEffect, useState, useTransition } from "react";

function SearchComponent() {
  const [keyword, setKeyword] = useState(""); // 입력값은 즉시 반영
  const [list, setList] = useState([]);
  const [isPending, startTransition] = useTransition();

  const items = Array.from({ length: 5000 }, (_, i) => `Item ${i + 1}`);

  const filterItems = (query) => {
    return items.filter((item) => item.includes(query));
  };

  const handleChange = (e) => {
    const value = e.target.value;
    setKeyword(value);
  };

  useEffect(() => {
    // 검색 결과 업데이트는 낮은 우선순위로 처리
    startTransition(() => {
      const filteredList = filterItems(keyword);
      setList(filteredList);
    });
  }, [keyword]);

  return (
    <div>
      <input
        type="text"
        value={keyword}
        onChange={handleChange}
        placeholder="Search..."
      />
      {/* 검색 결과가 지연될 때 로딩 메시지 표시 */}
      {isPending && <p>Updating list...</p>}
      <ul>
        {list.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

export default SearchComponent;
  • startTransition : 검색 결과 목록을 업데이트하는 작업을 낮은 우선순위로 처리합니다.
  • isPending : 트랜지션이 진행 중일 때 UI에 "Updating list..."를 보여줌으로써 로딩 상태를 보여줍니다.

useDeferredValue

useTransition은 함수 실행의 우선순위를 지정하는 반면, useDeferredValue는 값의 업데이트 우선순위를 지정합니다. 우선순위가 높은 작업을 실행하는 동안 useMemo와 유사하게 이전 값을 계속 들고 있으면서 업데이트를 지연시킵니다.

이 훅은 useMemo와 함께 사용하면 더 효과가 좋다. 종속된 값들을 memoize 시키면 불필요한 재 랜더링을 막으면서 하위 컴포넌트나 상태의 업데이트를 지연시킬 수 있습니다.


useDeferredValue 동작 원리

useDeferredValue가 값을 지연시키는 내부 동작은 React의 스케줄링 시스템우선순위 기반 작업 처리에 기반을 둡니다. 이 시스템은 React 18에서 도입된 Concurrent Mode(동시성 모드)에서 작동하며, 이는 React가 렌더링 작업을 더 유연하게 관리할 수 있도록 돕습니다. 구체적으로 useDeferredValue는 값의 업데이트를 지연시켜 중요한 작업을 먼저 처리하고, 덜 중요한 렌더링 작업을 나중에 처리하는 방식으로 작동합니다.

1 ) 스케줄링 작업

React는 상태나 값이 변경될 때마다 해당 변경을 작업 단위로 분리하여 처리합니다. 각 작업은 우선순위(priority)를 가지고 있습니다. 예를 들어, 사용자의 클릭이나 입력과 같은 상호작용은 높은 우선순위를 가지며, 데이터 처리나 값 필터링 등의 작업은 낮은 우선순위로 처리될 수 있습니다.

2 ) 현재 상태 유지

useDeferredValue는 값이 변경되더라도, 즉시 새로운 값으로 렌더링하지 않고 이전 값을 계속해서 사용합니다. 즉, 값 자체는 바로 변경되었지만, 렌더링이 지연되어 화면에 반영되는 것을 늦추는 방식입니다.

3 ) 값 업데이트 지연

React는 중요한 작업이 끝나거나 렌더링에 여유가 생겼을 때, 비로소 지연된 값을 사용하여 렌더링을 수행합니다. 이때 새로운 값이 기존 값과 달라졌다면, 그때서야 컴포넌트가 다시 렌더링됩니다. 중요한 점은 이 렌더링이 사용자의 인터페이스 상호작용을 방해하지 않도록, 낮은 우선순위로 처리된다는 것입니다.

4 ) 렌더링 최적화

useDeferredValue는 값이 자주 변하는 상황에서 이를 즉시 반영하지 않기 때문에, 불필요한 렌더링을 줄일 수 있습니다. 렌더링이 지연되면 React는 해당 작업을 백그라운드에서 처리하고, 중요한 UI 상호작용에 집중할 수 있습니다.


useDeferredValue 특징

  • 값의 업데이트 지연 : useDeferredValue는 값이 변하더라도 즉시 렌더링되지 않고 지연시켜 성능을 최적화합니다. 즉, 사용자가 입력하거나 값을 변경해도 해당 값이 바로 렌더링되지 않고, React가 여유가 있을 때 새로운 값으로 렌더링을 처리합니다.
  • 우선순위 기반 렌더링 : 값의 변경이 우선순위가 낮은 작업으로 처리되기 때문에, 더 중요한 작업이 먼저 처리됩니다. 즉, 사용자의 입력이나 클릭과 같은 중요한 상호작용은 우선적으로 처리되고, 덜 중요한 값의 렌더링은 나중에 처리됩니다.
  • useMemo와의 조합: useDeferredValueuseMemo와 함께 ****사용하면 더 효과적입니다. 자주 변하는 값들을 useDeferredValue로 지연시키고, 메모이제이션을 통해 종속된 값들을 메모이즈하면 불필요한 연산과 렌더링을 더 효과적으로 줄일 수 있습니다.

useDeferredValue 장점

  • 성능 최적화 : 값이 자주 변경되더라도 매번 리렌더링하지 않고, 렌더링을 지연시킴으로써 성능을 최적화할 수 있습니다. 특히, 대량의 데이터를 처리하거나 복잡한 연산을 수행하는 경우에 효과적입니다. 사용자가 빠르게 입력하거나 상호작용할 때, 해당 값을 바로 렌더링하지 않고 여유가 있을 때 렌더링함으로써 부드러운 성능을 유지할 수 있습니다.
  • UI 반응성 유지 : 사용자가 상호작용할 때, 값이 즉각적으로 반영되지 않아도 UI가 즉시 응답할 수 있도록 도와줍니다. 예를 들어, 사용자가 입력 필드에 텍스트를 빠르게 타이핑할 때 UI가 끊기지 않고 부드럽게 동작하며, 나중에 필터링이나 검색 작업이 진행됩니다. 즉, UI가 부드럽고 즉각적으로 반응할 수 있도록 해줍니다.
  • 불필요한 리렌더링 방지 : 자주 값이 변경될 때마다 매번 리렌더링이 일어나는 것을 방지할 수 있습니다. 이로 인해 불필요한 리렌더링이 줄어들고, 렌더링 성능이 크게 향상됩니다. 이는 특히 값이 자주 변경되거나 복잡한 컴포넌트 트리를 가진 상황에서 유용합니다.
  • 복잡한 UI에서 효과적 : 복잡한 UI에서는 값이 자주 변할 수 있습니다. 이때 useDeferredValue를 사용하면 값의 변화를 지연시켜 덜 중요한 부분을 비동기적으로 처리하여, 복잡한 UI에서도 성능 저하 없이 자연스러운 사용자 경험을 제공합니다.

useDeferredValue 단점

  • 값 반영의 지연 : 값의 변화가 바로 UI에 반영되지 않기 때문에, 사용자에게는 약간의 지연이 발생할 수 있습니다. 즉, 사용자가 변경한 값이 즉시 화면에 보이지 않으면, 사용자가 혼란스러워하거나 즉각적인 피드백을 받지 못하는 것처럼 느낄 수 있습니다. 이런 상황에서는 적절한 피드백(예: 로딩 상태 표시)이 필요할 수 있습니다.
  • 잘못 사용 시 사용자 경험 저하 : useDeferredValue를 잘못 사용하거나, 즉시 반영되어야 할 중요한 작업에 적용하면 사용자 경험을 저해할 수 있습니다. 예를 들어, 중요한 상호작용이나 즉각적인 반응이 필요한 경우에도 렌더링이 지연되면 사용자가 지연을 인지하게 되고, UI가 느리게 반응하는 것처럼 느껴질 수 있습니다.
  • 특정 시나리오에서 부적합 : 값의 즉각적인 반영이 중요한 애플리케이션(예: 실시간 데이터 업데이트, 게임 등)에서는 useDeferredValue가 적합하지 않을 수 있습니다. 이 경우에는 값을 즉시 반영해야 사용자 경험이 저하되지 않습니다.
  • 추가적인 코드 복잡성 : useDeferredValue를 적절히 활용하기 위해서는 코드에 추가적인 복잡성이 발생할 수 있습니다. 언제 값을 지연시킬지, 지연된 값을 어떻게 처리할지에 대한 전략을 잘 짜야 하며, 잘못된 사용은 성능을 저하시키거나 예상치 못한 동작을 초래할 수 있습니다.

useDeferredValue 사용 예시

useDeferredValue는 입력 필드의 값이 변경될 때, 해당 값을 즉시 반영하지 않고 지연시켜 불필요한 리렌더링을 방지하는 데 사용할 수 있습니다. 아래 예시에서는 사용자가 검색어를 입력할 때, 검색어 업데이트를 지연시켜 성능을 최적화하는 상황입니다.

import { useState, useDeferredValue, useEffect } from "react";

function App() {
  const [keyword, setKeyword] = useState("");
  const [list, setList] = useState([]);
  const deferredInput = useDeferredValue(keyword); // 지연된 값
  const items = Array.from({ length: 5000 }, (_, i) => `Item ${i + 1}`);

  const filterItems = (query) => {
    // 가상 데이터 필터링 로직 (예: 5000개의 항목을 필터링)
    return items.filter((item) =>
      item.toLowerCase().includes(query.toLowerCase())
    );
  };

  useEffect(() => {
    setList(filterItems(deferredInput));
  }, [deferredInput]);

  return (
    <div>
      <input
        type="text"
        value={keyword}
        onChange={(e) => setKeyword(e.target.value)}
        placeholder="Search..."
      />

      <ul>
        {list.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;
  • useDeferredValuekeyword 값을 지연시켜 성능을 최적화합니다. 즉, 사용자가 빠르게 입력할 때 매번 즉시 필터링하는 것이 아니라, 일정 시간 지연된 후 마지막 값으로 필터링 작업을 수행합니다. 이를 통해 UI 성능을 개선할 수 있습니다.
  • 검색어가 입력될 때마다 바로 목록을 업데이트하지 않고, 일정 시간이 지나면 업데이트되도록 처리하여 성능을 최적화합니다.

useTransition vs useDeferredValue

useTransitionuseDeferredValue는 React에서 성능 최적화를 위해 사용되는 두 가지 훅이지만, 그 목적과 동작 방식이 다릅니다.

useTransition은 상태 업데이트의 우선순위를 낮춰, 긴급하지 않은 작업(예: 필터링, 애니메이션 등)을 비동기적으로 처리하여 UI가 끊기지 않고 부드럽게 작동하도록 돕습니다. 이를 통해 중요한 작업(예: 사용자 입력)과 덜 중요한 작업을 구분하여 UI 응답성을 유지하는 데 중점을 둡니다.

반면, useDeferredValue는 값의 렌더링을 지연시켜 사용자가 느끼는 지연을 최소화하면서 덜 중요한 값의 변화를 비동기적으로 반영합니다. 즉, 값 자체는 즉시 업데이트되지만, 렌더링이 지연되기 때문에 UI 성능에 부담이 적습니다. 주로 값이 자주 변하는 상황에서 성능을 개선하는 데 사용됩니다.

이 두 훅은 각각 다른 용도에 맞게 사용되며, 애플리케이션의 성능과 사용자 경험을 크게 향상시킬 수 있습니다.


useTransitionuseDeferredValue
주요 목적긴급하지 않은 상태 업데이트를 낮은 우선순위로 처리하여 성능 최적화자주 변하는 값의 렌더링을 지연시켜 불필요한 리렌더링을 방지
사용 위치상태 업데이트 시, startTransition으로 감싸서 비동기 처리값의 업데이트가 빈번할 때, 값을 useDeferredValue로 감쌈
응답성중요한 상태 업데이트와 덜 중요한 작업을 구분해 UI가 끊김 없이 반응하도록 지원값의 변경을 지연시켜 UI 성능을 유지하면서 부드러운 동작을 제공
렌더링상태 업데이트가 비동기적으로 처리되어 UI의 즉각적 반응 보장렌더링이 지연되므로 값의 변화에 따른 불필요한 렌더링 최소화
사용 예시필터링, 대규모 데이터 처리, 애니메이션 등 긴급하지 않은 작업검색어 입력 시 렌더링 지연, 빠른 입력에서 발생하는 불필요한 렌더링 방지
profile
함께 개선하는 개발자

0개의 댓글