-
엑셀 파일 다운로드 기능 구현 여정 🚀개발여정 2024. 7. 12. 20:15
회사 프로젝트에서 사용자가 화면에서 데이터를 확인하는 것뿐만 아니라 검색 결과를 엑셀 파일로 다운로드할 수 있는 기능을 구현해야 했습니다.
완성 코드 미리보기 👀
https://codesandbox.io/p/sandbox/excel-download-poc-yv2y26?file=%2Fsrc%2FExcelSheet.ts%3A18%2C30
무엇을 고려해야 할까...? 🤔
어떻게 구현할지 고민하기에 앞서 만족되어야 하는 조건을 다음과 같이 고려해봤습니다.
- 클라이언트 사이드에서 엑셀에 필요한 데이터를 엑셀 파일로 변환할 수 있을 것
- 변환한 엑셀 파일을 저장할 수 있는 기능을 제공할 것
- 다양한 브라우저에서 엑셀 다운로드 기능이 지원될 것
- 타입스크립트를 지원할 것
어떻게 구현하지...? 🔍
엑셀다운로드 기능 구현을 위해서 라이브러리를 찾아봤었고 각 라이브러리의 문서를 읽어보며 어떤 특징이 있는지 빠르게 파악했습니다.
- exceljs
- Node.js 환경에서 사용되며, 서버 측에서 엑셀 파일을 생성하는 데 적합
- xlsx
- 복잡한 스프레드시트로부터 데이터를 추출하거나 새로운 스프레드시트를 생성하는 라이브러리
- 이미지, 그래프 피봇테이블, 함수 등의 일반적인 스프레드시트 작업들을 수행 가능
- 타입스크립트 지원
- 클라이언트 사이드에서 파일을 저장할 수 있도록 하는 유틸리티 함수를 제공
- 지원하는 브라우저는 다음과 같음
xlsx... 너로 정했다!!
위 두 라이브러리를 고려한 결과, 위에서 만족해야하는 조건 중 클라이언트 사이드에서 데이터를 엑셀로 변환하고 저장하는 기능을 가장 잘 지원하는 xlsx로 결정했습니다.
xlsx 라이브러리를 사용하면 다음과 같이 엑셀 파일 생성부터 다운로드까지 간단하게 구현할 수 있습니다.
// 1. 파일 생성 const workbook = XLSX.utils.book_new(); // 2. 시트 생성 const worksheet = XLSX.utils.aoa_to_sheet(excelData); // 3. 파일에 시트 추가 XLSX.utils.book_append_sheet(workbook, worksheet, sheetName, true); // 4. 파일 다운로드 XLSX.writeFile(workbook, fileName);
구현을 해보자!
이처럼 xlsx에서 제공하는 함수를 호출하여 간단히 기능을 구현할 수 있지만, 재사용성과 유지보수성을 고려했을 때 좀 더 구조적으로 만들 필요성을 느껴 다음과 같이 구조화를 했습니다.
- ExcelFile: 엑셀 파일 전체를 관리하고 생성하는 역할
- ExcelSheet: 각 시트의 데이터를 관리하고 시트를 생성하는 역할
- ExcelCell: 각 셀의 데이터를 관리하고 엑셀 셀 객체로 변환하는 역할
ExcelFile 클래스
import * as XLSX from 'xlsx'; import ExcelSheet from './ExcelSheet'; export default class ExcelFile { private workbook: XLSX.WorkBook = XLSX.utils.book_new(); // 엑셀 파일 private fileName: string; // 파일 이름 private fileExt: XLSX.BookType; // 파일 확장자 public constructor(fileName: string, fileExt: XLSX.BookType = 'xlsx') { this.fileName = fileName; this.fileExt = fileExt; } // 파일 전체 이름을 반환 public getFullFileName(): string { return `${this.fileName}.${this.fileExt}`; } // 엑셀 파일을 반환 public getWorkbook(): XLSX.WorkBook { return this.workbook; } // 엑셀 시트를 추가 public appendExcelSheet(sheet: ExcelSheet): void { XLSX.utils.book_append_sheet(this.workbook, sheet.getWorksheet(), sheet.getName(), true); } // 엑셀 파일 다운로드 public downloadFile(): void { XLSX.writeFile(this.getWorkbook(), this.getFullFileName()); } }
ExcelSheet 클래스
import * as XLSX from 'xlsx'; import ExcelCell from './ExcelCell'; export default class ExcelSheet { private sheetName: string; // 엑셀 시트 이름 private sheetData: ExcelCell[][]; // 엑셀 시트 데이터 public constructor(sheetName: string, sheetData: ExcelCell[][] = []) { this.sheetName = sheetName; this.sheetData = sheetData; } // 엑셀 시트 이름을 반환 public getName(): string { return this.sheetName; } // 한 행의 데이터를 추가 public appendRowData(row: ExcelCell[]): void { this.sheetData.push(row); } // 엑셀 시트를 반환 public getWorksheet(): XLSX.WorkSheet { return XLSX.utils.aoa_to_sheet(this.sheetData.map((row) => row.map((cell) => cell.toCellObject()))); } }
ExcelCell 클래스
import * as XLSX from 'xlsx'; export type ExcelValueType = string | number | boolean | Date; export type CellFormat = 'string' | 'number' | 'boolean' | 'date'; export default class ExcelCell { private value: ExcelValueType; private format: CellFormat; public constructor(value: any, format: CellFormat = 'string') { this.value = value; this.format = format; } private changeFormatType(format: CellFormat): XLSX.ExcelDataType { switch (format) { case 'string': return 's'; case 'number': return 'n'; case 'boolean': return 'b'; case 'date': return 'd'; } } public toCellObject(): XLSX.CellObject { return { v: this.value, t: this.changeFormatType(this.format), }; } }
이와 같이 구조화한 클래스들을 활용하여 다음과 같이 적용할 수 있었습니다.
// 1. acquire data const postsData = [ [new ExcelCell("Title"), new ExcelCell("Author")], [new ExcelCell("Post 1"), new ExcelCell("John")], [new ExcelCell("Post 2"), new ExcelCell("Jane")], ]; // 2. process and package data const excelFile = new ExcelFile("blog_posts", "xlsx"); const postsSheet = new ExcelSheet("Posts", postsData); excelFile.appendExcelSheet(postsSheet); // 3. download file excelFile.downloadFile();
마무리
이렇게 클래스를 이용하여 코드를 구성하여 엑셀 다운로드 기능을 완성할 수 있었습니다. 감사합니다.
'개발여정' 카테고리의 다른 글
React Native로 모바일과 웹을 한번에 개발하기 (0) 2024.07.19 Masonry Layout 구현 여정 🚀 (0) 2024.07.05 OCR 구현 여정 🚀 (0) 2024.06.28