useDeferredValue 의 보장 범위 — 값은 보장, 반영 시점은 미보장

검색어를 치면 입력창은 바로 바뀌어야 하는데, 아래 무거운 목록까지 매번 다시 그리면 입력이 밀린다. useDeferredValue(value)는 같은 값을 한 박자 늦게 따라오는 짝으로 하나 더 만들어준다.

언제: 같은 값을 쓰는 무거운 목록이 입력마다 다시 그려져 입력이 밀릴 때.

절차

  1. useDeferredValue(value) 로 늦은 짝을 만든다.
  2. 입력창은 빠른 쪽, 무거운 목록은 늦은 쪽을 쓴다.
  3. 둘이 벌어진 구간(isStale)을 알린다 — 보통 화면을 흐리게 해서 아직 이전 값이라는 것을 알린다.

확인: 입력창이 즉시 반응하고, 손을 멈췄을 때 목록이 따라잡는가.

늦은 쪽이 보여주는 값을 믿어도 되나

어떻게 동작하는지부터. 글자를 하나 치면 React가 화면을 두 번 그린다. 첫 번째는 빠른 쪽만 새 값으로(query는 이미 "ab"인데 deferredQuery는 아직 "a"), 두 번째는 늦은 쪽까지 새 값으로. 두 번째 그리기는 첫 번째가 끝나자마자 시작한다 — 기다리는 시간 같은 건 없다. 그리는 도중에 글자를 더 치면 그때까지 그린 걸 버리고 처음부터 다시 그린다. 다 그려서 화면에 올리기 전까지는 아무것도 안 보이고 useEffect도 실행되지 않는다.1

  • 없는 값을 만들지 않는다 — 늦은 쪽이 보여주는 건 언제나 _내가 실제로 쳤던 것 중 하나_다. 지어내지 않는다.
  • 결국 따라잡는다 — 손을 멈추면 따라잡는다. 계속 치는 동안은 다시 그리기가 반복해서 취소돼 영영 못 따라잡을 수도 있는데, 고장이 아니라 의도된 동작이다.

둘 다 위 동작에서 그대로 나온다 — 돌려줄 값을 새로 만드는 일이 없으니 지어낼 수가 없고, 다시 그리기가 늘 최신 값에서 출발하니 손을 멈추면 그 그리기가 끝까지 간다. 믿어도 된다. 다만 언제 따라잡을지는 모른다.

목록을 인라인으로 만들어도 늦은 쪽은 영향받지 않는다. items={data.map(...)}처럼 써서 참조가 매번 새로 생겨도 상관없다 — 그 목록이 늦은 쪽 계산에 아예 안 들어가기 때문이다. 같은 상황에서 망가지는 건 useMemo 캐시 쪽이다 → 578

Footnotes

  1. 두 단계 — “First, React re-renders with the new query ("ab") but with the old deferredQuery (still "a") … In the background, React tries to re-render with both query and deferredQuery updated.” / 타이밍 — “There is no fixed delay caused by useDeferredValue itself. As soon as React finishes the original re-render, React will immediately start working on the background re-render.” / 중단 — “The background re-render is interruptible: if there’s another update to the value, React will restart the background re-render from scratch.” / Effect — “does not fire Effects until it’s committed to the screen.” (useDeferredValue — React)

#570raw