React Suspense의 논점

27013 단어 Reacttech
React18 발표공식 블로그에 기재된 바와 같이 18은 앞으로 이터레이션 업그레이드에 더해 주변 생태계의 추종으로 완성, 즉 미완성 상태로 시작할 것이다.
다른 한편, 우리는 이미 React18을 설치하고 사용할 수 있다.오늘 이 가운데 예전보다 더 주목받고 난산 끝에 드디어 햇빛을 본 수퍼스가 보도했다.(※ 이전부터 한정 기능이 있었다)
아직 생태계를 따르지 않은 상황에서 수spense에 대해'수spense는 이렇게 사용한다'는 논의를 진행하기에는 이르기 때문에 이 기사에서 수spense에 대해 우리가 고려해야 할 논점이 무엇인지 생각해보겠습니다.
또 수spense의 용례가 많은데 이번에는 응용 엔지니어에게 가장 친근한 수spense for Datafetching을 주로 언급했다.

개시하다


실제 동작과 기본 지식은 다른 기사에 맡기고 API를 빠르게 확인해 주세요.
<Suspense fallback={<Spinner />}>
  <Comments />
</Suspense>
Comments에서suspend 상태(promise가throw 상태)에 있을 때 Comments 구성 요소가 렌더링되지 않고 Suspense의fallback을 표시합니다.기본적으로 매우 간단하다.

Suspense의 참조


Suspense의 공식 문서는 여러 곳에 흩어져 있어 정보를 수집하기 어렵지만 아래 링크를 보면 이해하기 쉽다.
Suspense의 현재 사양
https://github.com/reactjs/rfcs/blob/main/text/0213-suspense-in-react-18.md
Suspense의 앞으로.
https://github.com/reactwg/react-18/discussions/47#discussioncomment-847004
그럼 바로 Suspense의 논점을 살펴봅시다.

논점 1: Suspense 앞뒤 어셈블리 설계 변경 방법


Suspense를 가져오기 전에 구성 요소가 어떻게 변할까요?현재의 느낌으로 볼 때 나는'데이터가 데이터가 필요한 구성 요소에 최대한 접근해야 한다'는 방향이 더욱 강할 것이라고 생각한다.이는 colocation이라는 이름으로 유명한 React의 구성 요소 디자인의 상하문에서도 자주 언급되지만 앞으로는 Suspense에 따라 속도를 낼 것으로 보인다.
예를 들어 아래의 이런 코드는 지금 아주 잘 쓰여 있지 않습니까?요점은 ① 데이터 로드층과 ② 데이터 디스플레이층이 분리된다는 것이다.
function App() {
  // ①データロード層
  const [res] = useQuery({query: ListProducts})
  
  return (
    <div>
      <p>商品一覧</p>
      <div>
        // ②データディスプレイ層
        {res.fetching && <Spinner />}
        {res.data?.products && res.data.products.map(p => (
          <div>
            <div>{p.name}</div>
            <div>{p.price}</div>
          </div>
        ))}
      </div>
    </div>
  )  
}
Suspense를 사용하면 다음과 같이 코드가 표시됩니다.
function App() {
  return (
    <div>
      <p>商品一覧</p>
      <Suspense fallback={<Spinner />}>
        <ProductList />
      </Suspense>
    </div>
  )  
}

function ProductList() {
  // ①データロード層
  const [res] = useQuery({query: ListProducts})
  // ①データディスプレイ層
  return (
    {res.data?.products && res.data.products.map(p => (
      <div>
        <div>{p.name}</div>
        <div>{p.price}</div>
      </div>
    ))}
  )
}
데이터 로드층과 데이터 디스플레이층이 더욱 가깝다.이때 useQuery는suspense와 대응해야 하지만 이런 상황에서 useQuery의 기술을 Suspense 밖으로 내버려 두면 당연히 움직이지 않기 때문에 colocation은 자연스럽게 달성된 코드가 된다.

render-as-you-fetch와graphiql의fragment colocation에서


또한 데이터 취득층이 아니라 데이터 디스플레이층으로 쓴 것은 의도적인 것이다.조금 복잡하다면 렌더-as-you-fetch와graphiql의fragment colocation을 잘 사용하면 더 높은 층에서 얻은 처리를 정리하고 실제 파악한 순서대로 UI에 반영할 수 있다.이런 행위는 relay에서partial rendering이라고 불린다.
주변 프로그램 라이브러리 등의 대응이 필요하지만 가능하면 페이지의 맨 위에서 데이터 추출을 하고 데이터가 필요한 구성 요소에서 데이터가 표시된 훅을 사용하며 이 구성 요소는 각각 독립된Suspense로 포장합니다나는 이런 코드가 증가할 것이라고 생각한다.
지저분하게 쓰면 이런 기분이야?(특정한 라이브러리를 인상으로 쓰는 것이 아니라 허구의 라이브러리를 사용한다)
function App() {
  fetchQuery({query: RootQuery}) // 省略。fragmentなり下部レイヤーのデータリクエストをまとめる

  return (
    <div>
      <p>商品一覧</p>
      <Suspense fallback={<Spinner />}>
        <ProductList />
      </Suspense>
      <p>タグ一覧</p>
      <Suspense fallback={Spinner />}>
        <TagList />
      </Suspense>
    </div>
  )  
}

function ProductList() {
  const [data] = useDataSource({query: ListProducts})
  return (
    {data && data.products.map(p => (
      <div>
        <div>{p.name}</div>
        <div>{p.price}</div>
      </div>
    ))}
  )
}

function TagList() {
  const [data] = useDataSource({query: ListTags})
  return (
    {data && data.tags.map(p => (
      <div>
        <div>{p.name}</div>
      </div>
    ))}
  )
}
코드가 병렬로 Suspense를 설정했지만 복잡한 화면과 UI 최적화 상황에서 Suspense가 중첩될 수 있습니다.

마운트 상태의 UI가 글로벌 드라이어에서 로컬 드라이어로


이렇게 수spense를 이용하면 전 세계 적재구보다 국부적인 드러머를 사용한다.이전에는 까다로웠던 처리 부분인데 수spense를 가져올 때 사용자의 조작을 막지 않기 위해 글로벌한 드러머들은 대부분 피해야 할 UI로 수spense와 함께 현지 드러머를 퍼뜨리지 않았나.

논점2: 그럼 잘못된 처리는?


리액트 근처에 익숙한 분들은 이런 코드를 보셨을 거예요.
<ErrorBoundary fallback={<ErrorMessage />}>
  <Suspense fallback={<Spinner />}>
    <Comments />
  </Suspense>
</ErrorBoundary>
Error Boundary가 Commants에서 throw에 의해 포착된 오류를 fallback에 props로 표시하는 것을 목적으로 한 코드입니다.
실제로 React Core Team 중 하나인 bvaugon이 만든 프로그램 라이브러리를 사용하면 위 코드가 이동합니다.Suspense는 Promisse의 throw를 제외하고는 받지 않기 때문에 throw가 잘못된 경우 위에 있는 ErrorBoundary에 도착해서 받을 수 있다.
https://github.com/bvaughn/react-error-boundary
언뜻 보기에 이것은 매우 깨끗해 보일 것이다.하지만 이 코드의 이동을 위해서는 Comments에서 Throw Error가 필요합니다.이것은 Comments 구성 요소의 측면에서 볼 때 외부의 설치에 크게 의존한다.또한 전문가 라이브러리로서 구상한 이 실현을 감안하면 내부 실복을 의식적으로 사용할 수 없다는 것이 현실적이지 않다고 생각한다.(Suspense도 측면이 있다는 것을 부인할 수 없습니다.)
이 때문인지 최근에는 리액트의 핵심 팀이 Suspense for Datafetching 컨텍스트에서 Errorboundary를 언급하는 것을 보지 못했다.몇 가지 다른 메커니즘을 고려했을 수도 있지만 현재는 예전처럼 처리해야 한다.나는 앞으로의 동향을 주시하고 싶다.
참조:
https://github.com/reactwg/react-18/discussions/81

논점 3: 생태계의 추종


react-18 작업팀에 React18에 대응하는 프로그램 라이브러리를 정리한 게시물이 있는데 참고가 될 수 있지만 아직 멀었다고 생각합니다.
https://github.com/reactwg/react-18/discussions/113
Suspense와 관련해서는 최근 발표된 React 18에 대응하는 React Testing Library v13에 Suspense를 활용한 구성 요소의 테스트를 작성할 때 간단한query 패키지가 Suspense에 있는 구성 요소도 움직이지 않고 1 tick 지연시험의 cleanup이 순조롭게 돌아가지 못해 이상한 행동들만 하고 있다.
또한 Suspense는 아니지만 useSyncExternalStore의 대응 프로그램은 각 프로그램 라이브러리와 보기에 매우 어렵다.예를 들어 urql은 일시적으로 대응했지만 Revert가 React 외부 상태 관리층을 보유한 프로그램 라이브러리(form계,fetch계 등 사실상 상당수)가 어느 정도까지 대응할 필요가 있는지 밝혀진 것은 애매하다.
참조:
https://github.com/reactwg/react-18/discussions/113#discussioncomment-1647638
https://github.com/FormidableLabs/urql/issues/2309

논점 4:startTransition과의 관계


논점이 아닐 수도 있지만 이해하기 어려운 면이 있으니 설명해 주세요.
간단하게 생각해봐.
초기 데이터 추출: loading에서 Suspense를 사용하여 fallback 표시
재취득: startTransition을 사용하면 재취득 시 재로드 상태에 따라suspense는fallback을 표시하지 않고 새 데이터가 오기 전에 오래된 데이터를 보냅니다.
참조:
https://github.com/reactwg/react-18/discussions/94#discussioncomment-1406166

끝말


계속 이용하면 새로운 논점이 나오기 때문에 수시로 보완할 계획이다.혹시 눈치채신 분 있으면 꼭 메시지를 남겨주세요.

좋은 웹페이지 즐겨찾기