반응형
주의 : 해당 코드는 실전에서 사용할 만큼 완벽하지는 않음.
캐시와 동시성, 버퍼링은 컴퓨터 사이언스 있어 가장 어려운 문제 중 하나임.
실전에서는 검증된 라이브러리를 사용하자 (react-query)
캐시는 UI 상태가 아님.
리액트의 주 관심사는 UI를 위한 상태관리임.
UI를 위한 상태가 아닌 데이터는 관심사 분리의 대상임.
비동기 데이터 관리는 전적으로 개발자 판단임.
ex) 리덕스, apollo-graphql, relay, swr, 커스텀 컨텍스트, recoil
주의 2 : 렌더 메소드 안에서 프로미스를 만드는 것은 위험함
반드시 한번만 호출될 것이라는 가정이 없기 때문임.
따라서, 주의해서 사용해야함.
function MySuspendingComponent({value}) {
let resource = promiseCache[value]
if (!resource) {
resource = doAsyncThing(value)
promiseCache[value] = resource // 이런 식으로 여러번 호출 막아줌
}
return <div>{resource.read()}</div>
}
구현하기
1. 데이터가 있을 때만 데이터를 리턴하고, 그 외에는 promise를 리턴하는 헬퍼 메소드를 만든다.
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')
},
}
}
// fetch와 함께 사용
function createPokemonResource(pokemonName) {
return createResource(fetchPokemon(pokemonName))
}
2. 해당 메소드를 사용하는 프리젠테이셔널 컴포넌트를 구현한다.
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>
)
}
3. 캐시 컨텍스트를 만들고 사용한다.
캐시 컨텍스트는 글로벌로 사용하여, 전체에서 해당 아이템을 공유하도록 한다.
const PokemonResourceCacheContext = React.createContext()
function PokemonCacheProvider({children, cacheTime}) {
const cache = React.useRef({})
const expirations = React.useRef({})
// timeout 처리 함수
React.useEffect(() => {
const interval = setInterval(() => {
for (const [name, time] of Object.entries(expirations.current)) {
if (time < Date.now()) {
delete cache.current[name]
delete expirations.current[name]
}
}
}, 1000)
return () => clearInterval(interval)
}, [])
const getPokemonResource = React.useCallback(
name => {
const lowerName = name.toLowerCase()
// 캐시 등록
let resource = cache.current[lowerName]
if (!resource) {
resource = createPokemonResource(lowerName)
cache.current[lowerName] = resource
}
// 타임아웃 설정
expirations.current[lowerName] = Date.now() + cacheTime
return resource
},
[cacheTime],
)
return (
<PokemonResourceCacheContext.Provider value={getPokemonResource}>
{children}
</PokemonResourceCacheContext.Provider>
)
}
// 컨텍스트 사용 훅
function usePokemonResourceCache() {
const context = React.useContext(PokemonResourceCacheContext)
if (!context) {
throw new Error(
`usePokemonResourceCache should be used within a PokemonCacheProvider`,
)
}
return context
}
// 전체 앱을 감쌈.
function AppWithProvider() {
return (
<PokemonCacheProvider cacheTime={5000}>
<App />
</PokemonCacheProvider>
)
}
4. 캐시를 활용한다.
사용자는 캐시에서 가져오는지, 비동기 상태를 거쳐 가져오는지 몰라도 상관없다.
getPokemonResource는 해당 내용을 캡슐화한다.
function App() {
const [pokemonName, setPokemonName] = React.useState('');
const [startTransition, isPending] = React.useTransition(SUSPENSE_CONFIG);
const [pokemonResource, setPokemonResource] = React.useState(null);
const getPokemonResource = usePokemonResourceCache();
React.useEffect(() => {
if (!pokemonName) {
setPokemonResource(null)
return
}
startTransition(() => {
setPokemonResource(getPokemonResource(pokemonName))
})
}, [getPokemonResource, 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' 카테고리의 다른 글
Suspense with a Custom Hook (0) | 2021.12.24 |
---|---|
Suspense와 Image (0) | 2021.12.24 |
Render as you fetch : 렌더링과 데이터 페칭을 동시에 (0) | 2021.12.24 |
Simple Data-Fetching with React Suspense (0) | 2021.12.24 |
[짤막글][Concurrent Mode] 비동기 모드 사용 방법 (0) | 2021.12.23 |