useTransition은 UI를 block하지 않고 상태를 업데이트할 수 있는 React Hook이다.
컴포넌트 최상위 수준에서 useTransition을 호출하여 일부 상태 업데이트를 트랜지션으로 표시한다.
import { useTransition } from 'react';
function TabContainer() {
const [isPending, startTransition] = useTransition();
// ...
}
useTransition은 두가지 항목이 있는 배열을 반환한다.
- 보류 중인 전환이 있는지 여부를 알려주는 플래그 isPending
- 상태 업데이트를 전환으로 표시할 수 있는 startTransition 함수
startTransition function
useTransition에서 반환하는 startTransition 함수를 사용하면 상태 업데이트를 전환으로 표시할 수 있다.
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
}
scope: 하나 이상의 set함수를 호출하여 일부 상태를 업데이트하는 함수. React는 매개변수 없이 즉시 호출하고 scope함수를 호출하는 동안 동기적으로 예약된 모든 상태 업데이트를 트랜지션으로 표시한다. 이러한 전환은 블로킹되지 않으며 원치않는 로딩 표시를 하지 않는다.
- useTransition은 Hook이므로 컴포넌트나 커스텀 훅내부에서만 호출할 수 있다. 다른곳(예: 데이터 라이브러리)에서 트랜지션을 시작해야하는 경우 startTranstition을 사용할 수 있다.
- startTransition에 전달하는 함수는 동기식이어야 한다. React는 이 함수를 즉시 실행하여 실행하는 동안 발생하는 모든 상태 업데이트를 트랜지션으로 표시한다. 나중에 더 많은 상태 업데이트를 수행하려고 하면(예: 타임아웃에서), 트랜지션으로 표시되지 않는다.
- 트랜지션으로 표시된 상태 업데이트는 다른 상태 업데이트에 의해 중단된다. 예를 들어, 트랜지션 내에서 차트 컴포넌트를 업데이트한 다음 차트가 다시 렌더링되는 도중에 입력을 시작하면 리액트는 입력 업데이트를 처리한 후 차트 컴포넌트에 대한 렌더링 작업을 다시 시작한다.
- 트랜지션 업데이트는 텍스트 입력을 제어하는데 사용할 수 없다.
- 진행중인 트랜지션이 여러개 있는 경우, 리액트는 현재 트랜지션을 일괄처리한다. 이는 향후 릴리즈에서 제거도리 가능성이 높은 제한 사항이다.
Marking a state update as a non-blocking transition
컴포넌트의 최상위 레벨에서 useTransition을 호출하여 상태업데이트를 non-blocking transition으로 표시할 수 있다.
import { useState, useTransition } from 'react';
function TabContainer() {
const [isPending, startTransition] = useTransition();
// ...
}
useTransition은 정확히 두개의 항목이 있는 배열을 반환한다.
1. isPending flag. 보류중인 트랜지션이 있는지 알려주는 플래그
2. startTransition. 상태 업데이트를 전환으로 표시할 수 있는 startTransition 함수.
그런 다음 상태 업데이트를 다음과 같이 전환으로 표시할 수 있다.
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
}
트랜지션을 사용하면 느린 기기에서도 UI 의 업데이트의 반응성을 유지할 수 있다.
트랜지션을 사용하면 리렌더링 중에도 UI가 반응성을 유지한다. 예를 들어 사용자가 탭을 클릭했다가 마음이 바뀌어서 다른 탭을 클릭하면 첫번째 리렌더링이 완료될 때까지 기다릴 필요없이 다른 탭을 클릭할 수 있다.
Updating the parent component in a transition
useTransition의 호출에서 부모 컴포넌트의 상태를 업데이트할 수도 있다. 예를 들어 이 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 이벤트 핸들러 내에서 상태를 업데이트하므로 해당 상태 업데이트는 전환업데이트는 전환으로 표시된다. 선택한 탭을 업데이트하는 것은 전환으로 표시되므로 User Interaction을 차단하지 않는다.
Displaying a pending visual state during the transition
useTransition이 반환하는 isPending 값을 통해 전환이 진행중임을 사용자에게 표시할 수 있다. 예를 들어 탭 버튼은 특별한 'pending' 시각적 상태를 가질 수 있다.
function TabButton({ children, isActive, onClick }) {
const [isPending, startTransition] = useTransition();
// ...
if (isPending) {
return <b className="pending">{children}</b>;
}
// ...
Building a Suspense-enabled router
React 프레임워크나 라우터를 구축하는 경우 페이지 전환을 트랜지션으로 표시하는 것이 좋다.
function Router() {
const [page, setPage] = useState('/');
const [isPending, startTransition] = useTransition();
function navigate(url) {
startTransition(() => {
setPage(url);
});
}
// ...
두 가지 이유로 이 방법을 권장한다.
- 트랜지션은 중단될 수 있으므로 사용자가 다시 렌더링이 완료될때까지 기다리지 않고 바로 버튼을 클릭할 수 있다.
- 트랜지션은 원치 않는 로딩상태 표시를 방지할 수 있다.
- suspense-enabled 라우터는 기본적으로 전환 업데이트를 래핑할 것으로 예상된다.
Displaying an error to users with a error boundary
startTransition에 전달된 함수가 오류를 발생시키는 경우 에러 바운더리를 사용하여 사용자에게 오류를 표시할 수 있다. 에러 바운더리를 사용하려면 useTransition을 호출하는 컴포넌트를 에러 바운더리로 감싸면 된다. startTransition에 전달된 함수가 에러를 발생시키면 에러 바운더리에 대한 폴백이 표시된다.
Updating an input in a transition doesn't work
입력을 제어하는 상태 변수에는 transition을 사용할 수 없다.
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} />;
이는 트랜지션이 non-blocking이지만 change이벤트에대한 변경으로 입력을 업데이트하는 것은 동기적으로 이루어져야 하기 때문이다. 입력에 대한 응답으로 전환을 실행하려는 경우 두가지 옵션이 있다.
1. 입력 상태(항상 동기적으로 업데이트되는)와 전환 업데이트할 상태 변수를 별도로 선언할 수 있다. 이렇게 하면 동기 상태를 사용하여 입력을 제어하고 나머지 렌더링 로직에 전환 상태 변수(입력보다 "지연"되는)를 전달할 수 있다.
2. 대안으로, 상태 변수가 하나만 있고 실제 값보다 "지연"되는 useDefferedValue를 추가할 수 있다. 그러면 non-blocking 리렌더링이 새 값을 자동으로 "따라잡기"위해 트리거된다.
React doesn't treat my state update as a transition
상태 업데이트를 트랜지션으로 래핑할 때는 startTransition 호출 중에 상태 업데이트가 발생하도록 하세요.
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');
});
I want to call useTransition from outside a component
훅이기 때문에 컴포넌트 외부에서 useTransition을 호출할 수 없다. 이 경우 startTransition을 사용하자. 같은 방식으로 작동하지만 isPending은 제공하지 않는다.
The function I pass to startTransition executes immediately
// 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 ...
}
}