useTransition
은 UI를 방해하지 않고 상태를 업데이트해주는 리액트 훅이에요.
const [isPending, startTransition] = useTransition()
Reference | 레퍼런스
useTransition()
상태의 업데이트를 트랜지션으로 표시하려면 최상위 컴포넌트에서 useTransition
를 호출하세요.
import { useTransition } from 'react';
function TabContainer() {
const [isPending, startTransition] = useTransition();
// ...
}
Parameters | 파라미터(매개변수)
useTransition
은 매개변수가 필요하지 않아요.
Returns | 반환값
useTransition
은 정확히 두 아이템을 가진 배열을 반환해요.
- 보류중인 트랜지션이 있는지를 알려주는
isPending
플래그 - 트랜지션으로 상태를 업데이트한다고 표시하도록 도와주는
startTransition
function
startTransition
function | startTransition
함수
useTransition
에서 반환된 startTransition
함수는 상태 업데이트를 트랜지션으로 표시해줘요.
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
}
Parameters | 파라미터(매개변수)
scope
: 한 개 이상의set
함수를 호출하여 상태를 업데이트 시켜주는 함수. 리액트는 파라미터가 없는scope
를 즉시 호출하고scope
함수가 호출되는 동안 동기적으로 예정되어있는 모든 상태 업데이트를 트랜지션 표시해요. 이 업데이트들은 논블로킹이고 화면에 원치 않았던 로딩 표시를 따로 띄우지 않아요.
Returns | 반환값
startTransition
은 아무것도 반환하지 않아요.
Caveats | 주의사항
useTransition
은 훅이기 때문에 컴포넌트나 커스텀 훅의 내부에서만 호출될 수 있어요. 만약 트랜지션을 (데이터 라이브러리와 같은) 다른 곳에서 사용하고 싶다면 독립형startTransition
를 사용하세요.- 상태의
set
함수에 접근할 수 있을 때만 업데이트를 트랜지션으로 감쌀 수 있어요. 만약 prop이나 커스텀 훅 값에 반응하여 트랜지션을 실행하고 싶다면useDefferedValue
를 사용하세요. startTransition
에 전달한 함수는 동기적이어야해요. 리액트는 이 함수를 즉시 실행하고 실행 중에 발생하는 모든 를 트랜지션으로 표시해요. 만약 (timeout과 같이) 이후에 상태를 더 업데이트하고 싶다면 해당 상태는 트랜지션으로 표시되지 않을 거예요.- 트랜지션으로 표시된 상태 업데이트는 다른 상태 업데이트에게 방해를 받아요. 예를 들어 만약 트랜지션 안에서 차트 컴포넌트를 업데이트다가 차트가 리렌더링을 하는 도중에 입력창에 타이핑을 시작한다면 입력창의 업데이트를 핸들링한 후에 차트 컴포넌트를 리렌더링하는 작업을 재시작해요.
- 트랜지션 업데이트는 텍스트 입력을 컨트롤하는데 사용할 수 없어요.
- 만약 현재 진행중인 트랜지션이 여러개가 있다면 리액트는 이들을 함께 배치(batch)해요. 이는 향후 릴리즈에서는 삭제될 것으로 보이는 한계점이에요.
Usage | 용법
Marking a state update as a non-blocking transition | 상태 업데이트를 논블로킹 트랜지션으로 표시하기
상태 업데이트를 논블로킹 트랜지션으로 표시하려면 최상위 컴포넌트에서 useTransition
를 호출하세요.
import { useState, useTransition } from 'react';
function TabContainer() {
const [isPending, startTransition] = useTransition();
// ...
}
useTransition
은 정확히 두 아이템을 가진 배열을 반환해요.
- 보류중인 트랜지션이 있는지를 알려주는
isPending
플래그 - 트랜지션으로 상태를 업데이트한다고 표시하도록 도와주는
startTransition
function
아래와 같이 상태 업데이트를 트렌디션으로 표시할 수 있어요.
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
}
트랜지션은 느린 디바이스에서도 사용자 인터페이스의 업데이트를 반응적으로 유지하도록 해줘요.
트랜지션으로 표시된 UI는 리렌더링 도중에도 반응적으로 유지돼요. 예를 들어 사용자가 어떤 탭 버튼을 클릭했는데 마음이 바뀌어서 다른 탭을 클릭한다면 첫 번째 리렌더링이 끝나기를 기다리지 않고 새로운 작업을 할 수 있어요.
useTransition과 일반적인 상태 업데이트의 차이
1. 트랜지션으로 현재 탭 업데이트하기
이 예시에서 "Posts" 탭은 인위적으로 속도를 늦춰서 렌더링에 최소 1초가 소요돼요.
"Posts"를 클릭하고 즉시 "Contact"를 클릭해보세요. 이 행동이 "Posts"의 느린 렌더링을 방해하는 것을 확인하세요. "Contact" 탭은 즉시 보이게 돼요. 왜냐하면 이 상태 업데이트는 트랜지션으로 표시되어있고 느린 리렌더링은 사용자 인터페이스를 멈추지 않아요.
2. 트랜지션 없이 현재 탭 업데이트하기
이 예시에서도 "Posts" 탭은 인위적으로 속도를 늦춰서 렌더링에 최소 1초가 소요돼요. 이전 예시와는 달리 이 상태 업데이트는 트랜지션이 아니에요.
"Posts"를 클릭하고나서 즉시 "Contact"를 클릭하세요. 속도가 늦춰진 탭을 리렌더링 하는 동안 앱이 멈추고 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
의 이벤트 핸들러 안에서 업데이트하기 때문에 상태 업데이트는 트랜지션으로 표시돼요. 그래서 이전 예시에서처럼 "Posts"를 클릭하고 "Contact"를 즉시 클릭할 수 있는 거예요. 선택된 탭을 업데이트 하는 것은 트랜지션으로 표시되기 때문에 사용자와의 상호작용이 막히지 않아요.
Displaying a pending visual state during the transition | 트랜지션 동안 보류한 시각적 상태 표시하기
트랜지션이 진행 중이라는 사실을 사용자에게 알려주기 위해 useTransition
에서 반환된 isPending
이라는 불리안 변수를 사용할 수 있어요. 예를 들어, 탭 버튼은 특별히 "보류된" 시각적 상태에요.
function TabButton({ children, isActive, onClick }) {
const [isPending, startTransition] = useTransition();
// ...
if (isPending) {
return <b className="pending">{children}</b>;
}
// ...
탭 버튼 자체가 즉시 업데이트 되기 때문에 "Posts"를 클릭하는 것이 이 예시에서는 얼마나 반응적인지를 느껴보세요.
Preventing unwanted loading indicators | 원치 않는 로딩 표시를 하지 않기
이 예시에서 PostsTab
컴포넌트는 서스펜스가 가능한 데이터 소스를 사용하여 데이터를 받아요. "Posts" 탭을 클릭할 때, 가장 가까운 로딩 폴백이 나타나게 만드는PostsTab
컴포넌트를 연기해요.
로딩 표시를 하기 위해 체 탭 컨테이너를 숨기는 것은 사용자 경험상 기껍지 않아요. 만약 useTransition
을 TabButton
에 추가한다면 탭 버튼 안에서 보류된 상태를 보여줘요.
"Posts"를 클릭하는 것이 더 이상 전체 탭 컨테이너를 스피너로 대체하지 않는 것을 이 예시에서 확인하세요.
서스펜스를 트랜지션과 같이 사용하는 것에 대해서 더 알아보세요.
노트
트랜지션은 (탭 컨테이너와 같이) 이미 공개된 콘텐츠가 숨겨지지 않도록 충분히 "기다릴" 뿐이에요. Posts 탭이 중첩<Suspense>
바운더리를 가지고 있다면, 트랜지션은 더 이상 "기다리지" 않아요.
Building a Suspense-enabled router | 서스펜스를 사용할 수 있는 라우터 빌드하기
만약 리액트 프레임워크나 라우터를 빌드한다면, 페이지 네비게이션을 트랜지션으로 표시하세요.
function Router() {
const [page, setPage] = useState('/');
const [isPending, startTransition] = useTransition();
function navigate(url) {
startTransition(() => {
setPage(url);
});
}
// ...
이 방법을 추천하는 이유는 두 가지에요.
- 사용자가 리렌더링을 기다리지 않고 다른 곳을 클릭하면 트랜지션은 방해받을 수 있어요.
- 사용자가 네비게이션에서 부드럽게 넘어가지 못하는 상황을 피할 수 있도록 트랜지션이 원치 않는 로딩이 표시되지 않도록 막아줘요.
이 예시는 네비게이션에 트랜지션을 사용한 간단한 라우터 예시에요.
노트
서스펜스를 사용할 수 있는 라우터는 기본적으로 네비게이션 업데이트를 트랜지션으로 감싸야해요.
Displaying an error to users with a error boundary | 에러 바운더리로 사용자에게 에러 보여주기
실험실 기능
useTransition에서의 에러 바운더리는 현재 리액트의 카나리아 테스트 및 실험 채널에서만 사용할 수 있어요. 여기서 리액트의 릴리즈 채널에 대해 더 알아보세요.
startTransition
으로 전달된 함수가 에러를 던진다면 에러 바운더리로 사용자에게 에러를 보여줄 수 있어요. 에러 바운더리를 사용하기 위해서는 useTransition
을 호출한 컴포넌트를 에러 바운더리로 감싸세요. 함수가 startTransition
에 에러를 전달하면, 에러 바운더리의 폴백이 표시될 거예요.
Troubleshooting | 트러블슈팅
Updating an input in a transition doesn’t work | 트랜지션에서 입력창 업데이트가 작동하지 않아요.
입력창을 컨트롤하기 위한 상태 변수에 트랜지션을 사용할 수 없어요.
const [text, setText] = useState('');
// ...
function handleChange(e) {
// ❌ 컨트롤되는 입력창의 상태변수에 트랜지션을 사용할 수 없어요.
startTransition(() => {
setText(e.target.value);
});
}
// ...
return <input value={text} onChange={handleChange} />;
그 이유는 트랜지션은 논블로킹이지만 체인지 이벤트에 반응하는 입력창의 업데이트는 동기적으로 발생하기 떄문이에요. 만약 타이핑에 반응하는 트랜지션을 실행하고 싶다면 두가지 옵션 중 선택하세요.
- (항상 동기적으로 업데이트하는) 입력창의 상태와 트랜지션 안에서 업데이트 될 상태를 분리해서 선언하세요. 이는 동기적인 상태를 사용하는 입력창을 컨트롤하도록 해주고 (입력창에 "뒤쳐진) 트랜지션 상태 변수를 나머지 렌더링 로직에 전달해줘요.
- 다른 방법으로는 하나의 상태 변수를 가지고 실제 값에서는 "뒤쳐질"
useDefferedValue
를 추가하는 방법이 있어요. 이러면 자동적으로 새로운 값을 "따라잡기" 위하여 논블로킹 렌더링이 발생해요.
React doesn’t treat my state update as a transition | 리액트가 상태 업데이트를 트렌지션으로 취급하지 않아요.
트렌지션으로 상태 업데이트를 감쌀 때, startTransition
이 호출되는 동안 상태 업데이트가 발생되는지를 확인하세요.
startTransition(() => {
// ✅ startTrasition 호출이 일어나는 동안 상태 설정하기
setPage('/about');
});
startTransition
으로 전달한 함수는 동기적으로 작동해야해요.
아래와 같은 방식으로 업데이트를 트랜지션이라고 표시하면 안돼요.
startTransition(() => {
// ❌ startTrasition 호출이 일어난 후에 상태 설정하기
setTimeout(() => {
setPage('/about');
}, 1000);
});
그 대신 이렇게 하세요.
setTimeout(() => {
startTransition(() => {
// ✅ startTrasition 호출이 일어나는 동안 상태 설정하기
setPage('/about');
});
}, 1000);
이와 유사하게 아래와 같은 방식으로 업데이트를 트랜지션이라고 표시하면 안돼요.
startTransition(async () => {
await someAsyncFunction();
// ❌ startTrasition 호출이 일어난 후에 상태 설정하기
setPage('/about');
});
대신 이렇게 하면 작동해요.
await someAsyncFunction();
startTransition(() => {
// ✅ startTrasition 호출이 일어나는 동안 상태 설정하기
setPage('/about');
});
I want to call useTransition
from outside a component | 컴포넌트 외부에서 useTransition
을 호출하고 싶어요.
useTransition
은 훅이기 때문에 컴포넌트 외부에서는 호출할 수 없어요. 대신 이 경우에는 독립형 startTransition
를 사용하세요. 이 또한 같은 방법으로 작동하지만 isPending
지시자를 제공하지는 않아요.
The function I pass to startTransition
executes immediately | startTransition
에 전달한 함수가 즉시 실행돼요.
만약 아래 코드를 실행한다면 1,2,3을 출력해요.
console.log(1);
startTransition(() => {
console.log(2);
setPage('/about');
});
console.log(3);
이 코드는 1,2,3을 출력할 것이라고 예상되는 코드예요. startTransition
에 전달한 함수는 지연되지 않아요. 브라우저의 setTimeout
과는 다르게 콜백을 나중에 실행시키지 않아요. 리액트는 즉시 함수를 실행하지만 실행중이던 예정된 어떤 상태 업데이트든 트랜지션으로 표시돼요. 아래와 같이 동작한다고 생각할 수 있어요.
// 리액트의 동작 방법의 간단한 버전
let isInsideTransition = false;
function startTransition(scope) {
isInsideTransition = true;
scope();
isInsideTransition = false;
}
function setState() {
if (isInsideTransition) {
// ... 트랜지션 상태 업데이트를 스케줄링하기 ...
} else {
// ... 급한 상태 업데이트를 스케줄링하기 ...
}
}
'리액트 공식문서 | React Docs > Reference > react@18.2.0' 카테고리의 다른 글
[Hooks] useSyncExternalStore | useSyncExternalStore 훅 (1) | 2024.02.07 |
---|---|
[Hooks] useState | useState 훅 (1) | 2024.02.06 |
[Hooks] useRef | useRef 훅 (0) | 2024.02.04 |
[Hooks] useReducer | useReducer 훅 (1) | 2024.02.04 |
[Hooks] useOptimistic | useOptimistic 훅 (0) | 2024.02.03 |