리액트에서 무거운 연산이나 렌더링이 일어나면 메인 스레드가 블록 되기 때문에 다음 작업을 처리하지 못하거나, 프레임이 저하되는 문제가 발생할 수 있다.
이러한 문제를 해결하기 위해 사용하는 훅(hook)인 useDefferedValue 와 useTransition 을 정리하고 비교해보려 한다.
1. useDefferdValue
위와 같은 문제를 해결하기 위해 값의 업데이트 우선순위를 낮춰서, UI가 덜 중요한 업데이트를 늦게 처리하도록 도와주는 훅(Hook)이 useDefferedValue() 이다.
const deferredValue = useDefferedValue(value);
2. useTransition
useDefferedValue() 가 값을 래핑해서 업데이트 우선순위를 낮추다면,
함수를 래핑해서 업데이트 우선순위를 낮춰서, UI가 덜 중요한 업데이트를 늦게 처리하도록 도와주는 훅(Hook)이 useTransition() 이다.
const [inPending, startTransition] = useTransition();
- isPending : 상태 변화가 지연되고 있음을 알리는 boolean 값
- startTransition : 상태 변화를 일으키는 콜백함수를 전달받아 낮은 우선순위로 실행하는 함수
* startTransition 에 전달된 함수를 "Action" 이라 하기 때문에, startTransition 내에서 호출되는 모든 콜백함수의 이름은 action 이거나 "Action" 접미사를 포함해야 함
3. 예제
위에서 설명한 두 훅(Hook) 모두 상태 변화의 우선순위를 낮춘다는 공통점이 있다.
차이점은 useDefferedValue() 는 값을 래핑해서 사용한다면, useTransition() 은 함수를 래핑해서 사용한다는 것이다.
1. useDefferedValue()
function SearchBox({items}) {
const [input, setInput] = useState("");
const deferredInput = useDeferredValue(input);
const filteredItems = items.filter((item) =>
item.toLowerCase().includes(deferredInput.toLowerCase())
);
return (
<div>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<ul>
{filteredItems.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
- input 은 즉시 반영
- filteredItems 는 부하가 큰 작업으로 deferredInput 의 낮은 우선순위를 기준으로 뒤에 렌더링
useDeferredValue 는 값만 지연 시키는 것으로, 상태 업데이트 자체를 지연시키는 것은 아니다.
빈번하게 바뀌는 상태 값이 직접 트리거 하는 네트워크 요청에 대한 최적화는 debounce 를 쓰는 것이 효과적이다.
2. useTransition()
function Component() {
const [input, setInput] = useState("");
const [value, setValue] = useState("");
const [isPending, startTransition] = useTransition();
const onChange = (e) => {
startTransition(() => {
setInput(e.target.value);
});
setValue(e.target.value);
};
console.log({ input, isPending });
console.log({ value });
return <input type='text' onChange={onChange} />;
}
- setText 는 startTransition 함수로 래핑되어 있어 상태 변화의 우선수위가 낮아지고, isPending 은 true
- 다른 상태 업데이트가 완료된 후 setText 가 실행, isPending 은 false