프로덕트에서 UX 개선 항목으로 SkeletonUI 와 ProgressBar 요청사항이 들어왔다.
처음 진행해보는 작업이라 많은 검색을 해봤고, 절대 다수의 기술 블로그에서는 다음과 같이 설명하고 있었다.
1. 페이지 이동 시에 state 를 통해서 content 영역에 skeletonUI 를 렌더링한다.
2. fetch 이후 state 를 통해서 content 영역에 정상 데이터를 렌더링한다.
대충 코드로 나타내면 이렇게 되겠다.
const [content, setContent] = useState(<Skeleton />);
request('url', (resData) => {
setContent(<div>~~~~</div>);
});
유명한 스타트업의 기술블로그도 위와 같은 방식을 사용하고 있었다.
하지만 몇 가지 이유로 사뭇 다른 방식으로 개발하게 되었다. 그 이유는
첫째, 우리 프로덕트는 eCommerce로, 특성상 많은 이미지가 AWS를 통해 전달받아야 했다.
따라서 이미지 로딩에 많은 시간이 걸리는 상황이었다.
스켈레톤이 제거되는 시점이 fetch 이후가 아닌, image 가 전부 로딩이 되고 난 후가 되기를 원했다.
둘째, 일일히 추가하지 않더라도 _app.jsx 를 통해서 한번에 관리되기를 바랬다.
따라서 다음과 같은 방식이 되었다.
라우팅 시작 시에 >
1. initialProps 로 각 페이지가 어떤 타입의 스켈레톤을 그릴지 _app.jsx 로 전달
2. state를 통해 skeletonUI 생성
3. 각 페이지 접근 완료 후, 이미지 로딩이 완료되었는지 체크하여
4. 콜백으로 skeletonUI 를 제거하는 setState 실행
코드는 다음과 같다
// _app.jsx
funtion MyApp(props) {
const [isDOMImageLoaded, setIsDOMImageLoaded] = useState(false);
const [skeletonUI, setSkeletonUI] = useState();
...
const routeChangeStart = () => {
setIsDOMImageLoaded(false);
}
useEffect(() => {
Router.events.on('routeChangeStart', routeChangeStart);
return () => {
Router.events.off('routeChangeStart', routeChangeStart);
}
}, []);
useEffect(() => {
if (pageProps.skeletonType) {
if (pageProps.skeletonType === 'type1') {
setSkeletonUI(<SkeletonType 1/>);
}
...
}
}, [asPath]);
useEffect(() => {
if (isDOMImageLoaded === true) {
setSkeletonUI('');
}
}, [isDOMImageLoaded]);
...
return (
<>
...
<Component
{...pageProps}
isDOMImageLoaded={isDOMImageLoaded}
setIsDOMImageLoaded={setIsDOMImageLoaded}
/>
{skeletonUI}
...
</>
)
}
// imageLoadingFinish.js
const imageLoadingFinish = (callback = false) => {
if (typeof window === 'undefined') {
return false;
}
const imgs = document.images;
const len = imgs.length;
let counter = 0;
const incrementCounter = () => {
counter += 1;
if (counter === len) {
document.body.style.backgroundColor = 'white';
if (callback) {
callback();
}
}
};
[].forEach.call(imgs, (img) => {
if (img.complete) incrementCounter();
else img.addEventListener('load', incrementCounter, false);
});
return true;
};
export default imageLoadingFinish;
// somePage.jsx
const Main = ({setIsDOMImageLoaded}) => {
fetch('url', () => {
setPageContent(<div>~~~</div>);
imageLoadingFinish(() => {
setIsDOMImageLoaded(true);
});
});
}
Main.getInitialProps = () => {
...
return {
skeletonType: 'type1'
};
};
곧 퇴근시간이라 설명은 나중에 . .
'FE > React' 카테고리의 다른 글
Refactor[1] - Typescript (0) | 2021.11.08 |
---|---|
useState 가 아무튼 안될 때 (0) | 2021.10.18 |
배열을 useState에서 사용할 때 (4) | 2021.08.23 |
functions are not valid as a react child (1) | 2021.05.18 |
useEffect dynamic depth (0) | 2021.05.13 |