React 18에서 소개하는 useTransition과 useDeferredValue는 사용자 인터페이스의 반응성을 향상시키는 데 중점을 둔 새로운 Concurrent Rendering 기능입니다. 이러한 훅을 이해하려면 먼저 React의 동시성 모델(concurrency model)과 그것이 어떻게 사용자 경험을 개선하는지에 대한 이해가 필요합니다.
React의 동시성 모델은 애플리케이션의 자원을 효율적으로 사용하고 인터페이스의 반응성을 유지하기 위한 방법입니다. 이 모델은 렌더링 프로세스를 중단 가능한 작업으로 관리하여, 브라우저가 높은 우선 순위 작업(예: 입력 같은 사용자 인터랙션)을 먼저 처리할 수 있도록 합니다. 이는 특히 복잡한 애플리케이션과 데이터 로딩 상황에서 중요하며, 사용자 경험의 부드러움과 응답성을 크게 향상시킬 수 있습니다.
useTransition은 상태 업데이트가 화면에 즉시 반영되지 않아도 되는 경우에 유용합니다. 이 훅은 상태 업데이트를 'transition'으로 그룹화하고, 이러한 transition이 주요 사용자 인터랙션에 영향을 주지 않도록 합니다.
예를 들어, 사용자가 리스트를 필터링하는 상황을 상상해보세요. 입력하는 동안 사용자는 인터페이스의 부드러운 반응을 기대하지만, 실제 결과 목록(데이터 패칭이 포함될 수 있음)의 업데이트는 약간 지연되어도 괜찮습니다.
import { useState, useTransition } from 'react';
function MyComponent() {
const [query, setQuery] = useState('');
const [list, setList] = useState(initialList); // 초기 리스트 데이터
// transition을 설정합니다. timeoutMs는 선택적이며 여기서는 transition의 '긴급성'을 제어합니다.
const [startTransition, isPending] = useTransition({
timeoutMs: 3000,
});
const handleChange = (event) => {
const newQuery = event.target.value;
setQuery(newQuery);
// 비동기 transition 시작
startTransition(() => {
const filteredList = expensiveFilteringOperation(newQuery, initialList);
setList(filteredList);
});
};
return (
<div>
<input type="text" value={query} onChange={handleChange} />
{/* 로딩 표시기는 선택 사항이며, 로딩 상태가 지속적으로 보여질 필요가 있는 경우에 유용합니다. */}
{isPending ? <span>Loading...</span> : null}
<ul>
{list.map(item => <li key={item.id}>{item.text}</li>)}
</ul>
</div>
);
}
이 코드에서 expensiveFilteringOperation은 많은 리소스를 소모하는 가상의 함수입니다. useTransition은 이러한 연산이 사용자 입력과 같은 중요한 작업을 방해하지 않도록 돕습니다.
useDeferredValue 훅은 특정 값의 업데이트를 지연시킬 수 있도록 도와줍니다. 이는 'debouncing' 효과를 제공하여, 빠른 연속 입력과 같은 상황에서 고비용 계산을 지연시킬 수 있습니다.
import React, { useState, useDeferredValue } from 'react';
function MyComponent() {
const [text, setText] = useState('');
const deferredText = useDeferredValue(text, {
timeoutMs: 2000, // 최대 지연 시간
});
const handleTextChange = (event) => {
setText(event.target.value); // 실시간으로 입력 값 업데이트
};
return (
<div>
<input type="text" value={text} onChange={handleTextChange} />
{/* deferredText는 실제 text 값의 업데이트를 지연합니다. */}
<ExpensiveComponent text={deferredText} />
</div>
);
}
function ExpensiveComponent({ text }) {
// 여기서 '비싼' 연산을 수행합니다. 이 연산은 사용자가 빠르게 타이핑할 때 지연됩니다.
// ...
return <div>Computed Value: {text}</div>;
}
useDeferredValue는 값의 변경을 지연시키므로, 사용자가 빠르게 입력할 때 ExpensiveComponent의 렌더링이 즉시 발생하지 않습니다. 이는 애플리케이션이 부드럽게 반응하도록 돕는 반면, 백그라운드에서 '비싼' 연산을 수행할 수 있는 시간을 확보합니다.
두 훅 모두 사용자 경험을 향상시키는 데 중점을 둡니다. useTransition은 애플리케이션의 반응성을 유지하면서 상태 업데이트의 우선 순위를 제어하고, useDeferredValue는 빠른 상태 변화 사이에서 비싼 계산을 지연시키는 데 사용됩니다.
UseTransition에 callback 함수로 비동기를 사용하는게 의미가있을까요? 사용해도되는걸까요?