반응형
이미지의 placeholder가 없으면 레이아웃이 깨지기 쉽다.
이미지 캐시, 이미지 플레이스홀더 사용, Render as you fetch를 구현해보자.
function preloadImage(src) {
return new Promise(resolve => {
const img = document.createElement('img')
img.src = src
img.onload = () => resolve(src)
})
}
이미지 서스펜스 활용의 기본
1. 서스펜스 데이터를 렌더링할 뷰를 구현한다.
lazy로 코드 스플리팅하기 위해, default로 내보낸다.
(데이터와 코드 동시에 가져오기.)
// 리소스는 이렇게 가져옴.
function createPokemonResource(pokemonName) {
const data = createResource(fetchPokemon(pokemonName)) // 1
const image = createResource(preloadImage(getImageUrlForPokemon(pokemonName))) // 2
return {data, image}
}
// 캐시
const pokemonResourceCache = {}
// 1. 포켓몬 이름으로 정보 데이터 가져오기
function getPokemonResource(name) {
const lowerName = name.toLowerCase()
let resource = pokemonResourceCache[lowerName]
if (!resource) {
resource = createPokemonResource(lowerName)
pokemonResourceCache[lowerName] = resource
}
return resource
}
// 2. 이미지는 브라우저 내 캐시 공간을 이용할 수 있다.
function preloadImage(src) {
return new Promise(resolve => {
const img = document.createElement('img')
img.src = src
img.onload = () => resolve(src)
})
}
function PokemonInfo({pokemonResource}) {
const pokemon = pokemonResource.data.read()
return (
<div>
<div className="pokemon-info__img-wrapper">
<img src={pokemonResource.image.read()} alt={pokemon.name} />
</div>
<PokemonDataView pokemon={pokemon} />
</div>
)
}
2. 해당 코드를 Lazy하게 임포트한다
const PokemonInfo = React.lazy(() =>
import('../lazy/pokemon-info-render-as-you-fetch'),
)
3. Lazy하게 임포트한 view를 Suspense 위치에 넣어준다.
const SUSPENSE_CONFIG = {
timeoutMs: 4000,
busyDelayMs: 300,
busyMinDurationMs: 700,
}
function createPokemonResource(pokemonName) {
const data = createResource(fetchPokemon(pokemonName))
const image = createResource(preloadImage(getImageUrlForPokemon(pokemonName)))
return {data, image}
}
function App() {
const [pokemonName, setPokemonName] = React.useState('')
const [startTransition, isPending] = React.useTransition(SUSPENSE_CONFIG)
const [pokemonResource, setPokemonResource] = React.useState(null)
React.useEffect(() => {
if (!pokemonName) {
setPokemonResource(null)
return
}
startTransition(() => {
setPokemonResource(getPokemonResource(pokemonName))
})
}, [pokemonName, startTransition])
function handleSubmit(newPokemonName) {
setPokemonName(newPokemonName)
}
function handleReset() {
setPokemonName('')
}
return (
<div className="pokemon-info-app">
<PokemonForm pokemonName={pokemonName} onSubmit={handleSubmit} />
<hr />
<div className={`pokemon-info ${isPending ? 'pokemon-loading' : ''}`}>
{pokemonResource ? (
<PokemonErrorBoundary
onReset={handleReset}
resetKeys={[pokemonResource]}
>
<React.Suspense
fallback={<PokemonInfoFallback name={pokemonName} />}
>
<PokemonInfo pokemonResource={pokemonResource} />
</React.Suspense>
</PokemonErrorBoundary>
) : (
'Submit a pokemon'
)}
</div>
</div>
)
}
반응형
'FrontEnd' 카테고리의 다른 글
React Remix 신상 React framework 간단하게 살펴보기 (0) | 2021.12.27 |
---|---|
Suspense with a Custom Hook (0) | 2021.12.24 |
Suspense와 리소스 캐싱하기 (0) | 2021.12.24 |
Render as you fetch : 렌더링과 데이터 페칭을 동시에 (0) | 2021.12.24 |
Simple Data-Fetching with React Suspense (0) | 2021.12.24 |