Render as You Fetch
에 대해 쓰다가 suspense 예제를 조금 추가했는데, 갑자기 힘이 들어서 두 개로 나눠서 작성한다...
위와 같은 앱이 있다고 해보자. 버튼을 누르면 해당 데이터를 fetch 해온다. 방식은 Fetch-on-render으로 useEffect 후 useState에 데이터를 담고 rendering하는 방식이다.
전에도 말했던 것처럼 위의 방식은 요청 데이터가 waterfall로 온다.
데이터 요청 후 image가 차례로 들어오고 있다.
만약 아주 느린 network 환경이라고 가정해보자.
피카츄를 호출 후 리자드를 호출할 때, 데이터는 이미 리자드의 데이터로 변경되었는데, 이미지는 피카츄 이미지가 나왔다가 리자드 이미지가 나온다. 요청이 순차적으로 들어오기 때문에 이렇게 버퍼가 생겨버린 것이다!
위의 문제점을 수정하기 위해서는 이미지가 load 되기 전에 데이터가 render 가 안 되게 해야 한다.
그러기 위해선 해당 이미지에도 suspense를 걸어서 image의 src가 preload 해서 캐시에 잡히게 하는 것!! (살짝 헷갈린다)
예제를 살펴보자.
할 일은 두 가지이다.
캐시를 image의 src를 캐시하는 <Image>
컴포넌트 만들기
preload를 하는 함수 만들기
먼저 <Image>
를 만들고 src를 props로 받는다.
<Image>
는 <img/>
를 맵핑하는 컴포넌트다.
차례대로 살펴보면 cache가 있는 경우, 그대로 cache된 src를 보여주고 없으면 preloadImage
함수를 호출한다.
preloadImage
함수는 promise 객체를 리턴하는데, 내부적으로 img 태그를 만들고 resolved되면 onload한다.
img가 onload되면 브라우저 캐시에 image가 잡힌다. 즉, image가 브라우저 캐시에 잡히기 전까지 <Image>
를 suspense 한다.
브라우저에 image가 캐시된 후 <Image>
를 안전하게 return 하면 된다.
위와 같은 suspense가 가능한 이유는, createResource
함수가 내부적으로 promise를 받아서 promise 객체를 throw 하기 때문에 suspense에 걸리는 것이다. (음 어렵다...)
이제 네트워크가 느려고 img가 브라우저 캐시에 먼저 잡히고(preload)되고 Image 컴포넌트가 렌더 되어서 버퍼가 생기지 않는다.
정리하자면
<Image/>
가 렌더된다.imgSrcCache
에 캐시된 게 있나 체크한다. preloadImage
함수에 src를 넘겨 호출한다. preloadImage
는 내부적으로 선언한 <img/>
가 load되면 resolve된다.preloadImage
가 resolved됐다) 브라우저 캐시에도 잡혔다는 뜻<Image/>
가 렌더되면서 화면에 원하는 이미지가 나온다. 이제 이정도면 되지 않을까, 했지만 사실 워터폴은 아직 해결하지 못 했다. Render as You Fetch, 말 그래도 render와 fetch를 동시에 하려면 어떻게 할까? 바로, 동시에 패치하는 것이다!
생각해보면 데이터를 패치하고 데이터 안에 있는 src를 가져와서 이미지를 렌더링한다. 호출하려는 데이터의 이름(혹은 id)만 안다면 src를 따로 호출하면 되지 않을까!?
src만 따로 호출하는 함수를 만든다.
그리고 데이터를 패칭했던 함수를 조금 수정한다.
preload로 src를 직접 가져오도록 변경한다.
이제 view를 담당하는 컴포넌트에서 기존의 <Image/>
를 지우고 <img>
태그를 사용하면서 동시에 호출한 src 를 준다.
데이터와 이미지를 동시에 가져오는 것을 확인한다!!
와우... 진짜 체감상으로도 빨라졌다. 신기하고 아름다워...
** view에 보여줘야 하는 데이터, 이미지 크기가 크다면 해당 컴포넌트를 lazy 로 가져와도 된다.
업무하면서 Next에서 제공하는 이미지 컴포넌트 위주로 사용해서 이미지를 크게 생각하지 않았는데, 배우고 나니 짜릿하다... 이 세계 정말 깊구나...
참고