Suspense는 비동기를 동기처럼 보이게 만든다. 데이터가 아직 없으면 promise를 던지고, React가 그걸 받아 기다렸다가 다시 그린다.1 그래서 상태를 조율하는 대신 입력을 미루고 → 질의하고 → 그리는 순서로 끝난다.

언제: 비동기 데이터를 그리는데 로딩·에러가 늘면서 화면이 튈 때. 또는 새로 만들면서 어느 쪽으로 갈지 고를 때.

각각 들고 있으면: loading 과 error 가 따로 켜지고 꺼져 네 칸이 생긴다 — 둘 다 꺼짐은 성공, error 만 켜짐은 에러, loading 만 켜짐은 로딩, 둘 다 켜짐은 있을 수 없는 “로딩 + 에러”인데도 적을 수 있다. 그 칸이 한 프레임이라도 그려지면 화면이 튄다. 트리로 옮기면: 로딩(Suspense) → resolve → 성공(본문), 로딩 → reject → 에러(ErrorBoundary), 성공 → 입력 변경으로 promise 를 던지면 → 로딩. 상태는 셋 중 하나라 넷째 칸을 적을 수가 없다.

절차

  1. 검색어·결과·로딩 중인지·에러인지를 각각 들고 있는지 본다.
  2. 분기를 if/else에서 트리로 옮긴다 — 로딩은 <Suspense>, 에러는 <ErrorBoundary>, 성공은 본문.
  3. 본문에 loading·error를 인자로 넘기지 않는다. 안 넘겨도 가장 가까운 경계가 대신 잡기 때문이다2 — render는 결과 하나만 받는다.

확인: 있을 수 없는 조합을 적을 수 있는가. 적을 수 없으면 된 것이다.

함정: 스피너가 너무 빨리 뜨고 사라지는 건 이 축으로 안 고쳐진다 — 다른 축이다 → 573


곁가지 — 인자를 위로 들어올려 바깥이 처리하게 하는 이 모양은 대수적 효과(algebraic effects)의 handler에서 따왔다.3

stale-while-revalidate(두 시계·isStale) 부분은 570으로.

Footnotes

  1. “Technically, in that case the read() call throws a Promise (yes, throws a Promise — let that sink in). This “suspends” the execution. React catches that Promise, and remembers to retry rendering the component tree after the thrown Promise resolves.” (Algebraic Effects for the Rest of Us) ↩

  2. “When a component suspends, the closest parent Suspense component shows the fallback.” (Suspense — React) ↩

  3. try/catch와 비슷하다 — 중간 함수들은 몰라도 되고 가장 가까운 handler가 받는다. 다른 점은 handler가 값을 돌려주면 효과를 일으킨 자리로 되돌아가 실행이 이어진다는 것이다 — “It lets us jump back to where we performed the effect, and pass something back to it from the handler.” Suspense는 되돌아가지 않고 다시 그린다: “This isn’t an algebraic effect per se, even though this trick was inspired by them.” … “Of course, we can’t really resume execution in JavaScript later, but from React’s point of view, re-rendering a component tree when the Promise resolves is pretty much the same thing.” (Algebraic Effects for the Rest of Us) ↩

`#575`