본문 바로가기

FrontEnd

Render as you fetch : 렌더링과 데이터 페칭을 동시에

반응형

우리의 목적

asis
tobe

필요한 데이터를 최대한 즉시 가져오자

보통 데이터 fetch는 useEffect를 이용하는데, 이는 dom이 실제로 화면에 그려져 interactive한 이후이다.

(코드 로드 > 코드 파싱 > 코드 실행 > 컴포넌트 렌더링 > useEffect로 요청)

그리고 사용자는 렌더링 시간 + 데이터 가져오는 시간 만큼을 시리얼하게 기다려야 한다.

화면 그리기 전에 필요한 데이터를 가져올 수는 없을까?

Render as you fetch (렌더와 데이터 페치를 동시에)를 사용한다.

해당 방법을 위해선 suspense가 필요하다.

먼저, loading, error, success를 사용하는 useEffect훅을 대체한다.

성공하면 데이터, 그 외에는 promise를  throw 한다.

function createResource(promise) {
  let status = 'pending'
  let result = promise.then(
    resolved => {
      status = 'success'
      result = resolved
    },
    rejected => {
      status = 'error'
      result = rejected
    },
  )
  return {
    read() {
      if (status === 'pending') throw result
      if (status === 'error') throw result
      if (status === 'success') return result
      throw new Error('This should be impossible')
    },
  }
}

해당 데이터를 사용하는 view를 만든다.

function PokemonInfo({pokemonResource}) {
  const pokemon = pokemonResource.read()
  return (
    <div>
      <div className="pokemon-info__img-wrapper">
        <img src={pokemon.image} alt={pokemon.name} />
      </div>
      <PokemonDataView pokemon={pokemon} />
    </div>
  )
}

서스펜스와 에러바운더리로 감싼다.

에러바운더리는 자바스크립트 오류와 비동기 오류를 잡아 케이스에 따라 뷰를 보여주는 책임,

서스펜스는 비동기 로딩 상태와 성공 시 뷰를 보여주는 책임을 담당한다.

사용자 경험을 고려하여 컴포넌트를 배치한다.

function createPokemonResource(pokemonName) {
  return createResource(fetchPokemon(pokemonName))
}


function App() {
  const [pokemonName, setPokemonName] = React.useState('')
  const [pokemonResource, setPokemonResource] = React.useState(null)

  React.useEffect(() => {
    if (!pokemonName) {
      setPokemonResource(null)
      return
    }
    setPokemonResource(createPokemonResource(pokemonName))
  }, [pokemonName])

  function handleSubmit(newPokemonName) {
    setPokemonName(newPokemonName)
  }

  function handleReset() {
    setPokemonName('')
  }

  return (
    <div className="pokemon-info-app">
      <PokemonForm pokemonName={pokemonName} onSubmit={handleSubmit} />
      <hr />
      <React.Suspense
        fallback={
          <div className="pokemon-info">
            <PokemonInfoFallback name={pokemonName} />
          </div>
        }
      >
        <div className="pokemon-info">
          {pokemonResource ? (
            <PokemonErrorBoundary
              onReset={handleReset}
              resetKeys={[pokemonName]}
            >
              <PokemonInfo pokemonResource={pokemonResource} />
            </PokemonErrorBoundary>
          ) : (
            'Submit a pokemon'
          )}
        </div>
      </React.Suspense>
    </div>
  )
}
반응형