개요: 팀 ITO에서 사용하는 통계 조회, 엑셀 파일 제작 자동화를 수행하는 솔루션
개발 기한: 2023-01.01~ 2023-01.26 (1달)
맡은 역할: 프론트엔드 전체 개발 (프론트엔드 총 1명)
사용 기술: TypeScript, React, HTML, CSS
개발
개발 환경 설정
CRA를 사용해서 기본 개발 환경을 설정했다.
환경변수를 활용해서 .env.development
, .env.production
으로 분리하여 개발, 배포시 서버 URL 등을 선언하고 사용했다.
디자인 시스템
디자인 시스템으로 Ant-design 5.1.6
을 선택해서 최신 버전 Chrome에서 개발을 진행했다.
어느정도 개발이 진행되고 사내망(크롬 버전이 70~80으로 매우 낮음)에서 번들된 파일을 배포해서 테스트했을 때 UI가 대부분 깨져서 보여지는 문제가 있었다. Ant-design 5.1.6
의 경우 공식 사이트 기준으로 최신 2개 버전의 브라우저만 지원을 해서 생기는 문제였다.
Ant-design
의 버전을 낮춰서 개발하는 방법을 고려했지만 Ant-design
이 각 버전별로 어떤 버전의 브라우저를 지원하는지 제대로 알려주지 않아서 사용한 컴포넌트들의 CSS 정보들을 따로 CSS 파일로 빼고 직접 구현해서 사용했다.
구현한 컴포넌트들의 목록은 다음과 같다.
- Avatar, Button, Card, Col, Content, Divider, Header, Input, Layout, Menu, Modal, Spin, Sider, Col, Row, Timeline, Upload 등등..
라우팅 설정
react-router
를 활용해 라우팅 설정을 했다.
로그인이 필요한 페이지의 경우 AuthRoute
컴포넌트로 감싸주고, 그렇지 않은 로그인, 회원가입 등의 페이지는 감싸지 않았다.
AuthRoute
는 로그인여부를 확인해 로그인이 되어있다면 children
을 리턴하고, 그렇지 않으면 react-router-dom
의 Navigate
컴포넌트를 활용해서 로그인 페이지로 리라우팅해줬다.
// AuthRoute.tsx
const AuthRoute:React.FC<{children: any}> = ({children}) => {
return isLogin ? children : <Navigate replace to={{pathname: '/login'}}/>
}
라우트 전체를 Sider
, Header
가 포함된 Wrapper
컴포넌트로 감싸주고, 로그인 되어 있지 않은 경우 Wrapper
컴포넌트가 노출되지 않도록 처리했다.
/// App.tsx
function App() {
return (
<RecoilRoot>
<Router>
<Wrapper>
<Routes>
<Route path="/login" element={<Login />} />
<Route
path="/stats/chat-consulting"
element={<AuthRoute><Stats statType="chat-consulting" /></AuthRoute>}
/>
/// 생략
</Routes>
</Wrapper>
</Router>
</RecoilRoot>
)
}
/// Wrapper.tsx
const Wrapper: React.FC<WrapperProp> = ({ children }) => {
/// 생략
return (
<>
{isLogin ? (
<>{children}</>
) : (
<Layout>
<Header/>
<Sider/>
<Content>{children}</Content>
</Layout>
)}
</>
)
}
통계 조회 페이지 개발
통계조회 페이지는 iframe
태그를 활용하여 그라파나 통계 사이트 URL을 넣어줘서 간편하게 처리했다.
/// Stats.tsx
const Stats = ({ statType }: { statType: StatType }) => {
return (
<iframe
src={getWebViewSrc(statType)}
></iframe>
)
}
엑셀 파일 제작 페이지 개발
엑셀 파일 제작 페이지는 파일 제작 작업을 선택하고, 엑셀 파일을 업로드하는 입력
과 파일 제작 작업 결과를 미리보기로 확인하고, 파일을 다운로드할 수 있는 결과 2개의 페이지로 나뉜다.
따라서 status
란 상태를 선언하고 이를 활용해 엑셀 입력작업 중인지, 제작 작업중인지, 결과를 보여주는지 확인할 수 있게 했다.
/// Excel.tsx
type Status = "input" | "converting" | "result";
const Excel = () => {
const [status, setStatus] = useState<Status>("input");
}
입력창을 클릭하거나, 파일을 드래그 & 드롭하여 업로드할 수 있게 했다.
변환하기 버튼을 클릭시 파일에 대한 유효성 검사 진행 후에 파일 변환 api를 찌르도록 했다.
결과가 도착하면 성공시 엑셀 파일을 파싱하여 미리보기로 나타냈고, 실패시 사용자에게 실패 사유와 힌트를 보여주는 식으로 제작했다.
/// Excel.tsx의 covnertExcel 메소드
const convertExcel = (files: any[], scenarioId: number) => {
setStatus("converting");
processScenario(scenarioId, files)
.then(async (response) => {
if (response.status === 200) {
// ArrayBuffer -> File
const file = getFileFromBuffer(
new Uint8Array(response.data).buffer,
title
);
// 화면에 보여줄 수 있도록 파싱
const parsedFile = await parseExcel(file);
}
setStatus("result");
})
.catch((error) => {
setStatus("error");
});
};
엑셀 미리보기는 read-excel-js
라는 오픈소스 라이브러리를 활용해서 엑셀 파일의 셀 정보를 읽어오고, 이를 table
태그에 바인딩해서 보여주는 식으로 구현했다.
엑셀 파싱은 FileReader
와 Promise
를 활용했다. 파일 리더의 readAsArrayBuffer
메소드를 실행하면 onload
에 걸어놓은 함수가 작동한다. 얘네들은 비동기적으로 작동하기 때문에 파일을 전부 파싱한 후 결과 페이지로 이동하기 위해서 Promise
를 활용해서 파일 리더의 onload
에 파싱 작업을 진행하고, 다 완료된 후에 resolve
하도록 해서 동기적으로 코드가 진행될 수 있게 했다.
const parseExcel: (file: File) => Promise<any> = (file) =>
new Promise((resolve) => {
const wb = new ExcelJS.Workbook();
const reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = () => {
const buffer = reader.result;
wb.xlsx.load(buffer as Buffer).then((workbook) => {
let parsedFile: { title: string; sheets: any[] } = {
title: file.name,
sheets: [],
};
workbook.eachSheet((sheet, id) => {
let parsedSheet: { title: string; cells: any[] } = {
title: sheet.name,
cells: parseCells(sheet),
};
parsedFile.sheets.push(parsedSheet);
});
resolve(parsedFile);
});
};
});
정리
배운 점들
ㄱ. UI 컴포넌트 제작 경험
chrome
구버전에서 antd
의 컴포넌트들이 제대로 보여지지 않는 문제가 있었다.(디자인 시스템 부분에서 서술) 이 때문에 다양한 컴포넌트들을 antd
의 소스를 보면서 css
,jsx
등을 가져와서 제작했다. 이 경험을 통해 디자인 시스템에 대해서 좀 더 이해할 수 있었다.
ㄴ. TypeScript
타입스크립트를 프로젝트에 적용해봤다. 초반에 타입들을 선언해 놓고, 하는 부분에 있어서 까다로운 부분이 많지만, 컴파일 타임에 잠재적 오류들을 매우 많이 잡아줘서 좋았다. 안정적인 앱을 위해서는 필수적으로 사용해야 겠다는 생각을 했다.
ㄷ. 애니메이션 활용(keyframes 등) -> framer motion 공부중
업로드시 파일 목록에 글자가 노출될때 투명도가 점차 올라가면서 이름이 추가되고, 실패시에도 실패화면이 위에서 아래로 조금씩 내려오는 등의 애니메이션등을 넣었다. 애니메이션들이 과하지 않게 들어가면, 사이트의 사용성도 올라가고, 깔끔해진다. 좀 더 해당 분야에 대해서 공부하기 위해서 css animation
과 framer motion
등을 조금씩 공부하고 있다.
아쉬운 점들
ㄱ. TypeScript
any
의 많은 사용style?: any
,resultFile: any
등any
를 많이 사용했다. 추후 오류가 발생할 수 있기 때문에any
대신 명시적으로 타입을 지정해야 한다.React.FC의 많은 사용
React.FC는
childNode
가props
에 포함되게 된다. 이럴 경우child
가 들어가지 않는 컴포넌트 같은 경우, 런타임에서 오류가 발생할 수 있기 때문에 사용을 자제하고 딴 것으로 바꿔야겠다.추상화 부족(쪼갤 부분은 쪼개고 따로 함수로 만들어서 리팩토링할 필요가 있다.)
ㄴ. UI/UX
- 변환작업시 대기시간 20초 이상 소요
- 필요 파일의 경우 모달눌러서 확인해야 한다. 매우 불편하다.
ㄷ. 개발 환경
내가 만든 개발환경 fe-jhw-create-react-app
은 TypeScript
관련 설정이 안돼 있어 못 쓰고 CRA
를 사용했다. 내 개발환경에도 TypeScript
관련 설정을 추가하고, CRA
대신 Vite
등 다양하게 써보고, 비교해봐야겠다.
리팩토링할 것들
any
-> 타입 지정하기React.FC
-> 명시적 타입(ReactNode
등 활용) 지정추상화 부족(따로 함수로 빼주기)
변환작업시 대기시간 20초 이상(대용량 데이터 처리하므로) -> 로딩시 spinner로 처리했는데 사용자가 얼만큼 진행됬는지 알 수 없어서 답답함 -> progress bar등을 활용하여 얼만큼 진행됬는지 보여준다.
필요 파일의 경우 모달 눌러서 확인해야함 -> 업로드 할때 확인하고 체크해서 요구조건 만족시에만 보내도록 변경