본문 바로가기
Frontend/React

리액트 메이저 버전 업데이트 (1) - useTransition

by 잘먹는 개발자 에단 2024. 12. 11.

22년도에 있었던 메이저 버전 변경 ( 18 )이후 정말 오랜만에 메이저 업데이트( 19 )가 이루어졌다.

근데 아마 바로는 못쓸 것.... 다른 라이브러리 호환이 안될 수도..

 

1. 더이상 메모이제이션을 쓸 필요가 없어졌다. useMemo, useCallback

- 이제 리액트 컴파일러가 알아서 최적화한다.

 

2. 더 이상 forwardRef를 쓰지 않아도 된다.

forwardRef로 감싸지 않아도 props로 ref를 전달받을 수 있다.

 

3. use() hook이 등장할 줄 알고 좋아했으나...

이제 처음에 데이터 페칭해서 UI들 데이터로 채워넣는거 useEffect로 안해도 될 줄 알았는데.. 

언제 나오려나

 

4. 여러가지 훅들이 나옴

 

useTransition

[매개변수]

매개변수는 없다.

[반환]

2개의 항목이 있는 배열이다. 

첫번째는 isPending이라고 보류중인 Transition이 있는지 여부를 알려주는 플래그이다.  

두번째는 업데이트를 전환으로 표시할 수 있는 startTransition 함수이다. 

[startTransition]을 사용하면 업데이트를 Transition으로 표시할 수 있다. 

function TabContainer(){
	const [isPending, startTransition] = useTransition();
    const [tab, setTab] = useState('about');
    
    function selectTab(nextTab){
    	startTransition(()=>{
        	// 해당하는 작업을 하면 Pending 상태가 된다. 
            // 그럼 로딩을 표시하는 state를 따로 둘 필요가 없어, 코드가 간편해진다.
            setTab(nextTab);
        });
    }
}

startTransition을 통해서 하는 작업을 우리는 [action]이라고 부를 것이다. 

그래서 그런 규칙에 따라 startTransition 내부에서 호출되는 모든 콜백은 이름을 지정하거나 Action접미사를 포함해야 한다. 

예를 들어서 처음에 사용자 정보를 초기화하는 액션을 한다고 해보자. 

그럼 initUserInfoAction() 이런 식으로 뒤에 Action을 붙여서 이름을 지어야 하는 것이다. 

 

[그럼 action은 무엇인가]

여기서 말하는 액션이란 적어도 하나 이상의 setState 함수를 호출하여 일부 상태를 업데이트 하는 함수이다. 

매개변수 없이 즉시 호출되며, 함수 호출 중에 동기적으로 예약된 모든 상태 업데이트를 Transition으로 쳐버린다. 

참고로, UI를 블로킹하지 않으며, 블로킹에 대비한 로딩 표시기도 띄울 필요가 없다. 

필요하면 isPending 써서 너가해 ~ 

 

[가장 후순위이다]

이 startTransition에서 수행되는 상태 업데이트 작업은 항상 후순위로 배정된다. 이 때문에 UI를 블로킹하지 않는 것이다.

후순위로 놓지 때문에 텍스트 입력과 같은 UI업데이트를 우선적으로 수행할 수 있다. 

그렇기 때문에 텍스트 입력과 관련된 상태 업데이트는 여기서 할 수 없다. 입력했는데 다른 것 때문에 3-5 초 혹은 그 이후... 에 입력 값이 UI에 반영되는 대참사(?)가 일어날 수 있다. 

 

그럼 어떻게 쓰는지 예제를 한번 길게 쭉 보자. 

import {useState, useTransition} from 'react';
import {updateQuantity} from './api';

function CheckoutForm(){
	const [isPending, setIsPending] = useTransition();
    const [quantity, setQuantity] = useState(1);
    
    function onSubmit(newQuantity){
    	// 1번째 우선순위 작업 실행
    	startTransition(async function(){
        	const saveQuantity = await updateQuantity(newQuantity);
            
            // 2번째 우선순위 작업 실행
            startTransition(()=>{
            	setQuantity(savedQuantity);
            });
        });
    }
}

 

UI가 반응할거기 때문에 로딩 표시기를 굳이 표시할 필요가 없다. ....... 새로운 웹 트랜드인 것인가..?!

리액트 공식 문서에 직관적인 예시가 있는데, 별거아닌데 정말 쾌적한 듯..?

 

isPending이 있다는 것도 굉장히 편리하다. 없었으면 막 이러지 않았을까? 

(불편하겠지만 배칭은 논외로 합시더 크큼)

(아니 이게 몇줄 코드로 끝나는거)

.....
const [isLoad, setIsLoad] = useState(false);


const 가져와데이터 = async() => {
	try{
    	가져와
    }catch(예외){
    	처리해 예외
    }
}

useEffect(()=>{
	로딩 시작함 ㅋ
    가져와데이터
    로딩 끝남 ㅋ
},[]);

useEffect(()=>{
	if(isLoad){
    	// 로딩중일 때의 작업
    }
},[isLoad]);


return(
....
....
....
isLoad && 로딩 중인디? ㅋ UI

 

 

Suspense와 같이 쓰는 경우도 존재한다. 근데 누가 라우터 이렇게 씁니까..

import { Suspense, useState, useTransition } from 'react';
import IndexPage from './IndexPage.js';
import ArtistPage from './ArtistPage.js';
import Layout from './Layout.js';

export default function App(){
	return(
    	<Suspense fallback={<BigSpinner/>}>
        	<Router/>
        </Suspense>
    );
}

function Router(){
	const [page, setPage] = useState('/');
    const [isPending, startTransition] = useTransition();
    
    function navigate(url){
    	startTransition(()=>{
        	setPage(url);
        });
    }
    
    let content;
    if(page ==='/'){
    	content = (
        	<IndexPage navigate={navigate}/>
        );
    }else if(page === '/the-beatles'){
    	content = (
        	<ArtistPage artist=[{id:'the-beatles', name:'The Beatles'}]/>
        );
    }
    
    return(
    	<Layout isPending={isPending}>
        	{content}
        </Layout>
    );
}


function BigSpinner() {
  return <h2>🌀 Loading...</h2>;
}

 

 

[ErrorBoundary] 

- 만약에 전달된 함수가 오류를 발생시키는 경우에 ErrorBoundary를 사용해서 사용자에게 오류를 표시한다.

ErrorBoundary를 사용하려면 호출하는 구성 요소를 ErrorBoundary로 래핑한다. 

함수에 오류가 전달되면 ErrorBoundary의 fallback이 표시된다. 

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(){
	if(comment == null){
    	throw new Error('Example Error');
    }
}


function AddCommentButton(){
	const [pending, startTransition] = useTransition();
    
    return(
    	<button
        	disabled={pending}
            onClick={()=>{
            	startTransition(()=>{
                	addComment();
                });
            }}
        >Add comment</button>
    );
}

 

 

[몇가지 주의사항]

Transition에서 입력을 업데이트 하면 안된다.

UI논블로킹이지만 change 이벤트는 ui에 바로 반영해야하기 때문에 후순위 동작인 startTransition 액션에 넣을 수 없다. 

하지만 방법은 있다고 한다.

1. 2개의 state를 선언한다. 하나는 입력상태에 대한 것. 다른 하나는 Transition에서 업데이트하는 state. 그렇지만 입력보다 지연되는 건 어쩔 수 없으니 그냥 그렇게 하지말자. 

2. useDeferredValue 를 사용하는 것이다. 비차단 재렌더링을 트리거하여 새 값을 자동으로 따라잡는다. 

 

startTransition에 들어가는 함수는 동기식이어야 한다.

그러니까 이런 코드는 안된다. 대표적인 비동기 setTimeout으로 해보자. 

startTransition(()=>{
	setTimeout(()=>{
    	setPage('/about');
    },1000);
});

 

다음 코드로 바꿀 수 있다.

setTimeout(()=>{
	startTransition(()=>{
    	setPage('/about');
    });
},1000);

 

React는 내 상태 업데이트를 Transition으로 처리하지 않는다.

 

 

 

 

 

 

 

 

참고한 포스트

[React] 19 버전의 핵심 업데이트 사항 살펴보기

 

[React] 19 버전의 핵심 업데이트 사항 살펴보기

📖 들어가며현재 React 19 버전의 업데이트에 대해서 많은 관심을 가지고 있습니다. 여러 변경사항들이 많지만 이번 포스팅에서는 핵심 내용이라고 생각하는 부분을 포스팅하려 합니다. 즉, 기

yong-nyong.tistory.com

 

useTransition – React

 

useTransition – React

The library for web and native user interfaces

react.dev