프론트에서 백으로 요청을 보낸 사이의 로딩 공백을 ui 측면에서 개선해보자.
처음엔 로딩처리에 대한 별 생각이 없었다.
근데 신청서 제출할 때
1. 신청서 insert
2. cludinary upload
3. 신청서 파일 insert
하다 보니 몇 초 이상 걸린다... (나중에 성능 개선도 좀 봐야겠다)
맨 처음엔 tailwindcss의 spinner 아이콘을 가져와서 쬐깐한 처리를 했었다. 그러다 문득
기존에 next.js에서 app router할 때 loading.jsx를 추가했었다. 그래서 컴포넌트명은 WaitScreen
으로 정했다.
WaitScreen.jsx
'use client'
import { useMemo } from "react";
import PropTypes from "prop-types";
WaitScreen.propTypes = {
percent: PropTypes.number,
message: PropTypes.string,
};
// 필수로 지정하고 싶으면. PropTypes.number.isRequired
export default function WaitScreen(props) {
const {percent, message} = props;
const hasPercent = useMemo(() => percent !== undefined && percent > 0, [percent]);
return <div className="fixed inset-0 flex flex-col items-center justify-center bg-gray-800 bg-opacity-75 z-50">
<h1 className="text-lg font-semibold text-white mb-4">
{message ?? "잠시만 기다려 주세요"}
</h1>
<h1 className="text-lg font-semibold text-white mb-4">
{hasPercent && `진행률: ${props.percent}%`}
</h1>
<div className="flex items-center justify-center">
<div role="status" className="flex flex-col items-center">
<svg
aria-hidden="true"
className="w-16 h-16 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600"
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="currentColor"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentFill"
/>
</svg>
<span className="sr-only">Loading...</span>
</div>
</div>
</div>
}
하다 보니 로딩의 종류가 1개 아니었고 헷갈리기 시작했다.
로딩의 타입을 다시 정리해 보았다.
isLoading
을 받아와서 처리한다. A, B의 경우 화면 컴포넌트를 만들고, C라면 아이콘 정도만 해도 될 것 같았다. C의 경우 여러 컴포넌트가 다 렌더링될 때까지 전체 로딩을 띄운다면 사용자 입장에서 굉장히 답답할 것 같으니까.
(컴포넌트들끼리 각자 알아서 해라..)
그래서 WaitSpinner
, WaitScreen
총 2개를 만들었다.
프로젝트 구조
src/
├─ app/
│ │ ├─ loading.jsx/
├─ components/
│ │ ├─ WaitScreen.jsx/
│ │ ├─ WaitSpinner.jsx/
WaitSpinner.jsx
export default function WaitSpinner() {
return <div className="py-10">
<div className="flex items-center justify-center">
<div role="status" className="flex flex-col items-center">
<svg
aria-hidden="true"
className="w-16 h-16 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600"
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="currentColor"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentFill"
/>
</svg>
<span className="sr-only">Loading...</span>
</div>
</div>
</div>
}
전 프로젝트에서 axios를 쓸 때, 진행률을 실시간으로 표시하는 것을 보고 신기했다. 어떻게 한 거지?
axios.post(process.env.NEXT_PUBLIC_ROOT_DOMAIN + "/api/apply/test",
submitForm,
{
headers: {
"Content-Type": "multipart/form-data",
},
onUploadProgress // VIP:: 체크
})
//... 생략
}
onUploadProgress 함수를 만들어서 넘겨주면 progressEvent를 받아서 진행률을 처리할 수 있다.
처음에 이름이 저건줄 모르고 이름 바꿨더니 동작 안 함.
테스트용으로 전체 코드를 짰다.
간단한 POST API를 만들고 버튼을 누르면 요청~응답에 대한 진행률을 실시간으로 PROPS로 내려서 보여준다.
WaitScreenTest.jsx
import axios from "axios";
import { useState } from "react";
import WaitScreen from "../app/components/WaitScreen";
export default function WaitScreenTest() {
const [loading, setLoading] = useState(false);
const [loadPercent, setLoadPercent] = useState(0);
const onUploadProgress = (progressEvent) => {
const percentage = (progressEvent.loaded * 100) / (progressEvent.total || 1);
const percentCompleted = Math.round(percentage);
setLoadPercent(percentCompleted);
}
const onSubmit = (e) => {
e.preventDefault();
const submitForm = new FormData();
setLoading(true);
axios.post(process.env.NEXT_PUBLIC_ROOT_DOMAIN + "/api/apply/test",
submitForm,
{
headers: {
"Content-Type": "multipart/form-data",
},
onUploadProgress // 이름변경하지 말것. axios에서 사용하는 이름
})
.then((res) => {
if (res.status === 200) {
setLoading(false);
}
});
}
return <>
{loading && <WaitScreen percent={loadPercent} message={loadPercent + " "} />}
<button onClick={onSubmit}>눌러봐</button>
</>
}
결과적으로 <WaitScreenTest/ >
의 경우 응답이 빨라서 퍼센트 변화 감지가 파박하고 사라진다.
신청서로 테스트해보니 0-47-100%식으로 증가하는 걸 볼 수 있었다.
읽어주신 분들께 감사드립니다.