useTransition
은 UI를 차단하지 않고 state를 업데이트할 수 있는 React 훅입니다.const [isPending, startTransition] = useTransition()
참조
useTransition()
컴포넌트의 최상위 레벨에서
useTransition
을 호출하여 일부 state 업데이트를 트랜지션으로 표시합니다.import { useTransition } from 'react'; function TabContainer() { const [isPending, startTransition] = useTransition(); // ... }
Parameters매개변수
useTransition
은 매개변수를 받지 않습니다.Returns반환값
useTransition
은 정확히 두 개의 항목이 있는 배열을 반환합니다:- 보류 중인 트랜지션이 있는지 여부를 알려주는
isPending
플래그
- state 업데이트를 트랜지션으로 표시할 수 있는
startTransition
함수
startTransition
function
useTransition
이 반환하는 startTransition
함수를 사용하면 state 업데이트를 트랜지션으로 표시할 수 있습니다.function TabContainer() { const [isPending, startTransition] = useTransition(); const [tab, setTab] = useState('about'); function selectTab(nextTab) { startTransition(() => { setTab(nextTab); }); } // ... }
매개변수
scope
: 하나 이상의set
함수를 호출하여 일부 state를 업데이트하는 함수. React는 매개변수 없이scope
를 즉시 호출하고scope
함수 호출 중에 동기적으로 예약된 모든 state 업데이트를 트랜지션으로 표시합니다. 이는 논블로킹이고, 원치 않는 로딩을 표시하지 않을 것입니다.
반환값
startTransition
은 아무것도 반환하지 않습니다.주의사항
useTransition
은 훅이므로 컴포넌트나 커스텀 훅 내부에서만 호출할 수 있습니다. 다른 곳(예: 데이터 라이브러리)에서 트랜지션을 시작해야 하는 경우, 대신 독립형startTransition
을 호출하세요.
- 해당 state의
set
함수에 접근할 수 있는 경우에만 업데이트를 트랜지션으로 감쌀 수 있습니다. 일부 prop이나 커스텀 훅 값에 대한 응답으로 트랜지션을 시작하려면, 대신useDeferredValue
를 사용해보세요.
startTransition
에 전달하는 함수는 동기식이어야 합니다. React는 이 함수를 즉시 실행하여, 실행하는 동안 발생하는 모든 state 업데이트를 트랜지션으로 표시합니다. 나중에 더 많은 state 업데이트를 수행하려고 하면(예: 타임아웃), 트랜지션으로 표시되지 않습니다.
- 트랜지션으로 표시된 state 업데이트는 다른 state 업데이트에 의해 중단됩니다. 예를 들어, 트랜지션 내에서 차트 컴포넌트를 업데이트한 다음, 차트가 다시 렌더링되는 도중에 입력을 시작하면 React는 입력 업데이트를 처리한 후 차트 컴포넌트에서 렌더링 작업을 다시 시작합니다.
- 트랜지션 업데이트는 텍스트 입력을 제어하는 데 사용할 수 없습니다.
- 진행 중인 트랜지션이 여러 개 있는 경우, React는 현재 트랜지션을 함께 일괄 처리합니다. 이는 향후 릴리스에서 제거될 가능성이 높은 제한 사항입니다.
사용법
업데이트를 논블로킹 트랜지션으로 표시하기
컴포넌트의 최상위 레벨에서
useTransition
을 호출하여 state 업데이트를 논블로킹 트랜지션으로 표시하세요.import { useState, useTransition } from 'react'; function TabContainer() { const [isPending, startTransition] = useTransition(); // ... }
useTransition
은 정확히 두 개의 항목이 있는 배열을 반환합니다:- 보류 중인 트랜지션 이 있는지 여부를 알려주는
isPending
플래그를 선택합니다.
- state 업데이트를 트랜지션으로 표시할 수 있는
startTransition
함수입니다.
그런 다음, 다음과 같이 state 업데이트를 트랜지션으로 표시할 수 있습니다:
function TabContainer() { const [isPending, startTransition] = useTransition(); const [tab, setTab] = useState('about'); function selectTab(nextTab) { startTransition(() => { setTab(nextTab); }); } // ... }
트랜지션을 사용하면 느린 디바이스에서도 사용자 인터페이스 업데이트의 반응성을 유지할 수 있습니다.
트랜지션을 사용하면 리렌더링 도중에도 UI가 반응성을 유지합니다. 예를 들어, 사용자가 탭을 클릭했다가 마음이 바뀌어 다른 탭을 클릭하면 첫 번째 리렌더링이 완료될 때까지 기다릴 필요 없이 다른 탭을 클릭할 수 있습니다.
useTransition과 일반 state 업데이트의 차이점
1. 트랜지션에서 현재 탭 업데이트하기
2. 트랜지션 없이 현재 탭 업데이트하기
트랜지션에서 상위 컴포넌트 업데이트하기
useTransition
호출에서도 부모 컴포넌트의 state를 업데이트할 수 있습니다. 예를 들어, 이 TabButton
컴포넌트는 onClick
로직을 트랜지션으로 감쌉니다:export default function TabButton({ children, isActive, onClick }) { const [isPending, startTransition] = useTransition(); if (isActive) { return <b>{children}</b> } return ( <button onClick={() => { startTransition(() => { onClick(); }); }}> {children} </button> ); }
부모 컴포넌트가
onClick
이벤트 핸들러 내에서 state를 업데이트하기 때문에 해당 state 업데이트는 트랜지션으로 표시됩니다. 그렇기 때문에 앞의 예시처럼 ‘Posts’을 클릭한 다음 바로 ‘Contact’를 클릭할 수 있습니다. 선택한 탭을 업데이트하는 것은 트랜지션으로 표시되므로 사용자 상호작용을 차단하지 않습니다.트랜지션 중에 ‘보류중’ state 표시하기
useTransition
이 반환하는 isPending
boolean 값을 사용하여 트랜지션이 진행 중임을 사용자에게 표시할 수 있습니다. 예를 들어, 탭 버튼은 특별한 ‘pending’ state를 가질 수 있습니다:function TabButton({ children, isActive, onClick }) { const [isPending, startTransition] = useTransition(); // ... if (isPending) { return <b className="pending">{children}</b>; } // ...
이제 탭 버튼 자체가 바로 업데이트되므로 ‘Posts’를 클릭하는 반응이 더 빨라진 것을 확인할 수 있습니다:
원치 않는 로딩 표시 방지하기
이 예제에서
PostsTab
컴포넌트는 Suspense가 도입된 데이터 소스를 사용하여 일부 데이터를 가져옵니다. “Posts” 탭을 클릭하면 PostsTab
컴포넌트가 중단되어 가장 가까운 로딩 폴백이 나타납니다로딩 표시를 위해 전체 탭 컨테이너를 숨기면 UX가 어색해집니다.
TabButton
에 useTransition
을 추가하면 대신 탭 버튼에 ‘보류중’ state를 표시할 수 있습니다.‘Posts’를 클릭하면 더 이상 전체 탭 컨테이너가 스피너로 바뀌지 않습니다:
Note
트랜지션은 탭 컨테이너와 같이 이미 노출된 콘텐츠를 숨기지 않을 수 있을 만큼만 “대기”합니다. Posts 탭에 중첩된
<Suspense>
바운더리가 있는 경우 트랜지션은 이를 “대기”하지 않습니다.Suspense가 도입된 라우터 구축하기
React 프레임워크나 라우터를 구축하는 경우 페이지 네비게이션을 트랜지션으로 표시하는 것이 좋습니다.
function Router() { const [page, setPage] = useState('/'); const [isPending, startTransition] = useTransition(); function navigate(url) { startTransition(() => { setPage(url); }); } // ...
두 가지 이유로 이 방법을 권장합니다:
- 트랜지션은 중단 가능하므로, 사용자는 다시 렌더링이 완료될 때까지 기다리지 않고 바로 클릭할 수 있습니다.
- 트랜지션은 원치 않는 로딩 표시를 방지하여, 사용자가 네비게이션 시 갑작스럽게 이동 하는 것을 방지할 수 있습니다.
Note
Suspense가 도입된 라우터는 기본적으로 네비게이션 업데이트를 트랜지션으로 감싸고 있을 것이라고 간주합니다.
문제 해결
트랜지션에서 input 업데이트가 작동하지 않습니다
input을 제어하는 state 변수에는 트랜지션을 사용할 수 없습니다:
const [text, setText] = useState(''); // ... function handleChange(e) { // ❌ Can't use transitions for controlled input state startTransition(() => { setText(e.target.value); }); } // ... return <input value={text} onChange={handleChange} />;
이는 트랜지션은 논블로킹이지만 변경 이벤트에 대한 응답으로 input을 업데이트하는 것은 동기적으로 이루어져야 하기 때문입니다. input에 대한 응답으로 트랜지션을 실행하려면 두 가지 옵션이 있습니다:
- input의 (항상 동기적으로 업데이트되는) state와 트랜지션 실행시 업데이트할 state 변수를 각각 선언할 수 있습니다. 이를 통해 동기 state를 사용하여 input을 제어하고, (input보다 “지연”되는) 트랜지션 state 변수를 나머지 렌더링 로직에 전달할 수 있습니다.
- 또는 하나의 state 변수를 가지고, 실제 값보다 “지연”되는
useDeferredValue
를 추가할 수 있습니다. 그러면 새로운 값을 자동으로 “따라잡기” 위해 논블로킹 리렌더를 촉발합니다.
React가 state 업데이트를 트랜지션으로 처리하지 않습니다.
state 업데이트를 트랜지션으로 감쌀 때는
startTransition
호출 중 state 업데이트가 발생해야 합니다:startTransition(() => { // ✅ Setting state *during* startTransition call setPage('/about'); });
startTransition
에 전달하는 함수는 동기식이어야 합니다.이와 같은 업데이트는 트랜지션으로 표시할 수 없습니다:
startTransition(() => { // ❌ Setting state *after* startTransition call setTimeout(() => { setPage('/about'); }, 1000); });
대신 이렇게 할 수 있습니다:
setTimeout(() => { startTransition(() => { // ✅ Setting state *during* startTransition call setPage('/about'); }); }, 1000);
마찬가지로 업데이트를 이와 같은 트랜지션으로 표시할 수 없습니다:
startTransition(async () => { await someAsyncFunction(); // ❌ Setting state *after* startTransition call setPage('/about'); });
대신 이 방법은 작동합니다:
await someAsyncFunction(); startTransition(() => { // ✅ Setting state *during* startTransition call setPage('/about'); });
컴포넌트 외부에서 useTransition
을 호출하고 싶습니다
훅이기 때문에 컴포넌트 외부에서
useTransition
을 호출할 수 없습니다. 이 경우 대신 독립형 startTransition
메서드를 사용하세요. 동일한 방식으로 작동하지만 isPending
표시기를 제공하지 않습니다.startTransition
에 전달한 함수는 즉시 실행됩니다
이 코드를 실행하면 1, 2, 3이 인쇄됩니다:
console.log(1); startTransition(() => { console.log(2); setPage('/about'); }); console.log(3);
1, 2, 3을 출력할 것으로 예상됩니다.
startTransition
에 전달한 함수는 지연되지 않습니다. 브라우저의 setTimeout
과 달리 나중에 콜백을 실행하지 않습니다. React는 함수를 즉시 실행하지만, 함수가 실행되는 동안 예약된 모든 state 업데이트는 트랜지션으로 표시됩니다. 이렇게 작동한다고 상상하면 됩니다:// A simplified version of how React works let isInsideTransition = false; function startTransition(scope) { isInsideTransition = true; scope(); isInsideTransition = false; } function setState() { if (isInsideTransition) { // ... schedule a transition state update ... } else { // ... schedule an urgent state update ... } }