React 공식문서 도장깨기(6) - useTransition
이번에 정리해볼 내용은 React 공식문서에서 useTransition에 대해 정리하고자 한다. React 18이 도입되고 나서 생긴 개념인데 이번에 정리해보면서 처음 알게 된 개념이다. 추후 프로젝트에서 한번 사용해보자.
<참고 레퍼런스>
https://react-ko.dev/reference/react/useTransition
useTransition – React
The library for web and native user interfaces
react-ko.dev
<useTransition>
개념: useTransition은 UI를 차단하지 않고 state를 업데이트할 수 있는 React 훅 이다. 사용 방법은 useState를 사용하듯이 컴포넌트의 최상위에서 선언 해주면 된다.
import { useTransition } from 'react';
function TabContainer() {
const [isPending, startTransition] = useTransition();
// ...
}
✅ isPending
개념: 보류 중인 트랜지션이 있는지 여부를 알려주는 용도. 즉, 상태 변화가 지연되고 있음을 알리는 boolean 타입이다.
✅ startTransition
개념: state 업데이트를 트랜지션으로 표시할 수 있는 함수이다. startTransition 함수를 사용하면 state 업데이트를 트랜지션으로 표시할 수 있는 특징이 있다. 또한 useState의 일부 set 함수를 호출하여 일부 state를 업데이트 할 수 있다. 이를 scope라고 하며 scope 함수 호출 중에 동기적으로 예약된 모든 state 업데이트를 트랜지션으로 표시한다.
트랜지션 업데이트 할 때는 텍스트 입력을 제어하는 데 사용할 수 없다. 예를 들면 input같은 것을 다룰 때 이다.
❗중요:
1. 트랜지션으로 표시된 state 업데이트는 다른 state 업데이트에 의해 중단될 수있다. 예를 들어, 트랜지션 내에서 차트 컴포넌트를 업데이트한 다음, 차트가 다시 렌더링되는 도중에 입력을 시작하면 React는 입력 업데이트를 처리한 후 차트 컴포넌트에서 렌더링 작업을 다시 시작한다. 즉, 특정 함수 내에서 set 함수를 2개 사용한다 가정할 때 1개가 만약 startTransition 함수로 래핑되어 있을 경우 다른 1개의 상태변화가 일어난 후에 startTransition 함수로 래핑되어 있는 상태변화가 일어난다.(우선순위가 낮아진다고 생각하자. 그래서 주로 우선순위가 낮은 것들을 startTransition에 넣는 형태로 사용한다.)
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
}
2. 트랜지션을 사용하면 리렌더링 도중에도 UI가 반응성을 유지한다. 두개의 탭이 있다고 가정해보자. 첫번째 탭을 누른 후 두번째 탭을 누를 때 첫번째 리렌더링이 끝날 때 까지 기다리는게 아닌 기다릴 필요 없이 다른 탭을 클릭 할 수 있다. 그래서 만약 첫번째 탭에서 의도적으로 렌더링하는 데 최소 1초가 걸리도록 설정한다 해도 1초를 기다리지 않고 두번째 탭을 클릭할 수 있다.
<트랜지션에서 상위 컴포넌트 업데이트하기>
부모 컴포넌트에서 onClick 핸들러를 통해 set함수(state를 업데이트 하는 함수)를 발동시킨다고 가정해보자. 이후 자식 컴포넌트에 onClick을 인자로 보낼 경우 자식 컴포넌트는 해당 onClick함수에 대해서 startTransition의 콜백함수로 설정할 수 있다.
< 부모 컴포넌트>
import { useState } from 'react';
import TabButton from './TabButton.js';
import AboutTab from './AboutTab.js';
import PostsTab from './PostsTab.js';
import ContactTab from './ContactTab.js';
export default function TabContainer() {
const [tab, setTab] = useState('about');
return (
<>
<TabButton
isActive={tab === 'about'}
onClick={() => setTab('about')}
>
About
</TabButton>
<TabButton
isActive={tab === 'posts'}
onClick={() => setTab('posts')}
>
Posts (slow)
</TabButton>
<TabButton
isActive={tab === 'contact'}
onClick={() => setTab('contact')}
>
Contact
</TabButton>
<hr />
{tab === 'about' && <AboutTab />}
{tab === 'posts' && <PostsTab />}
{tab === 'contact' && <ContactTab />}
</>
);
}
< 자식 컴포넌트>
import { useTransition } from 'react';
export default function TabButton({ children, isActive, onClick }) {
const [isPending, startTransition] = useTransition();
if (isActive) {
return <b>{children}</b>
}
return (
<button onClick={() => {
startTransition(() => {
onClick(); //startTransition으로 래핑
});
}}>
{children}
</button>
);
}
<원하는 않은 로딩 방지하기 - Suspense 이용한다고 가정>
TabContainer가 Suspense 컴포넌트를 사용한다고 가정해보자. fallback 속성을 이용하여 로딩 중일 때 표시할 내용을 정의했는데 여기서는 로딩 아이콘과 함께 Loading 메시지를 보여준다. (Suspense개념이 나온 김에 다음 블로그 내용은 Suspense에 대해서 정리해야겠다😂 ) 현재는 TabButton 컴포넌트에 startTransition을 적용하지 않은 상태이다.
import { Suspense, useState } from 'react';
import TabButton from './TabButton.js';
import AboutTab from './AboutTab.js';
import PostsTab from './PostsTab.js';
import ContactTab from './ContactTab.js';
export default function TabContainer() {
const [tab, setTab] = useState('about');
return (
<Suspense fallback={<h1>🌀 Loading...</h1>}>
<TabButton
isActive={tab === 'about'}
onClick={() => setTab('about')}
>
About
</TabButton>
<TabButton
isActive={tab === 'posts'}
onClick={() => setTab('posts')}
>
Posts
</TabButton>
<TabButton
isActive={tab === 'contact'}
onClick={() => setTab('contact')}
>
Contact
</TabButton>
<hr />
{tab === 'about' && <AboutTab />}
{tab === 'posts' && <PostsTab />}
{tab === 'contact' && <ContactTab />}
</Suspense>
);
}
export default function TabButton({ children, isActive, onClick }) {
if (isActive) {
return <b>{children}</b>
}
return (
<button onClick={() => {
onClick();
}}>
{children}
</button>
);
}
만약 로딩 표시를 보여주기 싫다면 onClick에다가 startTransition으로 래핑 해주자. 탭 버튼에 ‘보류중’ state를 표시할수 있고 이때 isPending을 이용한다.
import { useTransition } from 'react';
export default function TabButton({ children, isActive, onClick }) {
const [isPending, startTransition] = useTransition();
if (isActive) {
return <b>{children}</b>
}
if (isPending) {
return <b className="pending">{children}</b>;
}
return (
<button onClick={() => {
startTransition(() => {
onClick();
});
}}>
{children}
</button>
);
}
<에러 메시지를 이용하여 사용자에게 오류 표시하기>
startTransition에 전달된 함수가 에러를 발생시키면, 에러 바운더리를 사용하여 사용자에게 에러를 표시할 수있다. useTransition을 호출하는 컴포넌트를 에러 바운더리로 감싸고 Suspense의 fallback과 동일하게 로딩 중 보여줄 UI를 제공한다. 표시되는 조건은 다음과 같다.
startTransition에 전달된 함수가 에러를 발생시키는 경우
import { useTransition } from "react";
import { ErrorBoundary } from "react-error-boundary";
export function AddCommentContainer() {
return (
<ErrorBoundary fallback={<p>⚠️Something went wrong</p>}>
<AddCommentButton />
</ErrorBoundary>
);
}
function addComment(comment) {
// For demonstration purposes to show Error Boundary
if (comment == null) {
throw new Error("Example Error: An error thrown to trigger error boundary");
}
}
function AddCommentButton() {
const [pending, startTransition] = useTransition();
return (
<button
disabled={pending}
onClick={() => {
startTransition(() => {
//addComment()가 에러를 발생시킬 때 fallback 발동
addComment();
});
}}
>
Add comment
</button>
);
}
<setTimeOut과 startTransition의 콜라보>
두 코드의 중요한 차이점은 상태변환이 startTransition에서 일어나는지 아닌지다. 첫번째 코드 같은 경우 1000ms 이후에 setTimeout이 실행되고 그때 상태가 변경되므로, 이 변경은 startTransition에 의해 관리되지 않고 일반적인 상태 업데이트처럼 동작하게 된다. 이렇게 되면 React는 여전히 해당 상태 업데이트를 중요 작업으로 인식하게 된다.
startTransition(() => {
// ❌ Setting state *after* startTransition call
setTimeout(() => {
setPage('/about');
}, 1000);
});
그래서 다음과 같이 setTimeout에서 상태를 업데이트할 때는 startTransition을 그 안에 포함해 비동기 상태 전환을 유지해야 한다.
setTimeout(() => {
startTransition(() => {
// ✅ Setting state *during* startTransition call
setPage('/about');
});
}, 1000);
<다음 공식문서 주제>
https://react-ko.dev/reference/react/Suspense
<Suspense> – React
The library for web and native user interfaces
react-ko.dev