React 18에서 새롭게 등장한 hooks
React 18이 나온 지도 시간이 꽤 지났습니다. 이제는 어느 정도 안정화 단계에 접어들었다고 판단되어서 새로운 프로젝트를 시작하면서 도입하기로 결정했는데요, React 18에서 새롭게 등장한 hooks의 기능이 꽤나 강력합니다. 한 번 정리해두면 좋을 것 같아서 정리해보았습니다.
우선 이번에 등장한 hooks는 총 5개입니다. 그중에 2개는 라이브러리를 위해 제공되는 것이고, 3개는 어플리케이션 단에서 사용할 수 있는 것이에요.
Library hooks
해당 hooks는 라이브러리를 위해 제공되는 hooks로, 일반적인 Application 단에서는 사용할 필요가 없어서 자세히 알아보지는 않았습니다. 그냥 이런 게 있다더라 정도만 이해하시고 상세한 설명은 공식문서나 다른 블로그를 참고해 주세요.
1. useSyncExternalStore
2. useInsertionEffect
New hooks
이제부터 본격적으로 새롭게 사용될만 한 hooks입니다. useId
, useDeferredValue
, useTransition
로 총 3개입니다.
useId
const id = useId()
이 hook은 전역적으로 고유한 hash를 생성해 줍니다. 여기까지만 딱 보고 어쩐지 list를 map으로 렌더링할 때 key로 사용하면 되겠다고 생각하시는 분들이 있을 것 같은데, 친절한 공식문서는 그렇게 쓰지 말라고 아예 못을 박아주었습니다.
오오 선견지명
그러면 언제 쓰면 될까요?
주로 Client component와 Server component 간의 hydration 단계에서 발생할 수 있는 mismatch를 피하려고 할 때 사용할 수 있다고 합니다. Server side에서 생성된 id와 Client side에서 생성된 id가 달라 발생하는 mismatch error를 해결하도록 권장 된다는군요. 예를 들자면 아래와 같은 상황이 되겠습니다.
function UserInfo() { // const id = Math.random() 원래는 이런 식으로 id를 부여했다면, const id = useId() // 앞으로는 이렇게 사용할 수 있습니다. return ( <div> <label htmlFor={id}>UserName</label> <input id={id} type='text' /> </div> ) }
useDeferredValue
const [value, setValue] = useState('') const deferredValue = useDeferredValue(value) const results = useMemo(() => <Results value={deferredValue} />, [deferredValue]) return ( <> <input value={value} onChange={(e) => setValue(e.target.value)} /> <Suspense fallback={...}> {results} </Suspense> </> )
이 hook은 기존에 렌더링 차단을 방지하려는 목적으로 사용되었던
debounce / throttle
을 대체할 수 있는 기능입니다. 기존의 방법은 딜레이가 고정적이라서 사용자의 의도와는 상관없이 반드시 설정된 시간만큼을 기다려야만 했었죠. 가령 debounce에 2000의 타임아웃을 부여한다면, 사용자는 무조건 입력 완료 후 2초가 지나야 결과를 받아볼 수 있었습니다.하지만 이 hook을 사용하면 입력이 완료되는 즉시 업데이트 작업을 수행하도록 변경할 수가 있습니다. 위의 예시 코드의 경우라면, value가 변경되더라도 deferredValue에는 변경된 값이 업데이트되는 게 아니라 이전 값을 리턴해줍니다. 그러다가 사용자의 입력이 완료되었다고 판단되면 그제야 새로운 값을 리턴합니다. 편리하죠?
그렇지만 이 기능을 사용할 때는 주의사항이 있습니다. 이 hook의 관심사는 어디까지나 해당 값의 동일 여부이므로, 해당 값으로 인한 component의 업데이트를 제한하기 위해서는
useMemo
와 함께 사용할 것을 권장하고 있습니다.useTransition
const [isPending, startTransition] = useTransition()
사실 이 hook은 바로 위에서 언급했던 useDeferredValue와 비슷한 기능을 수행합니다. React 18에서는 렌더링을 두 가지로 구분하고 있는데요, Urgent rendering과 Transition rendering이 그것입니다.
- Urgent rendering: 사용자의 Typing 등 일반적으로 즉시 업데이트 될 것으로 기대되는 렌더링 - Transition rendering: 검색 결과 추천 등 반드시 즉시 업데이트 되지는 않아도 될 렌더링
이들 중 Transition rendering을 사용하도록 강제하는 hook이 바로 useTransition입니다. 이렇게 말로만 해서는 잘 이해가 되지 않을 것 같아 간단하게 code를 준비해보았습니다. 기본 상태에서는 어떤 기능도 적용되어 있지 않고 주석으로 처리해두었으니 직접 주석을 해제하면서 확인해보세요 : )
Codesandbox
useTransition
vs useDeferredValue
?
그럼 왜 비슷한 hook이 두 개나 존재할까요? 그것은 서로의 용도가 다르기 때문입니다. 그러니까 둘을 동시에 사용할 필요는 없겠죠. 제가 여러가지로 실험을 하면서 닿게 된 결론은 아래와 같습니다.
- useTransition은 상태를 제어하는 코드가 내부에 포함되어 있을 경우에 사용
- useDeferredValue는 상태를 스스로 제어할 수 없는 경우(e.g. 외부에서 내려주는 props나 libarary의 return 값 등)에 사용