티스토리 뷰
01. 개요
일을 하면서 생각보다 엑셀을 많이 이용한다는 것을 느꼈다. 그래서 엑셀 다운로드와 업로드 기능을 복습할 겸 간단하게 구현해보았다.
02. 작성한 코드 및 실행 흐름
엑셀 다운로드를 위해서 xlsx 라이브러리와 file-saver 라이브러리가, xlsx-populate 라이브러리를 이용하였다.
02-1. 버튼 클릭 시 실행할 downloadExcelFile 함수
import { useState } from "react";
import { cloneDeep } from "lodash";
import * as XLSX from "xlsx";
import ProductList from "./dummyData/ProductList";
import GameCharacters from "./dummyData/GameCharacter";
import CustomerOrders from "./dummyData/CustomerOrderList";
import DataTable from "./Components/DataTable";
import { excelDownload } from "./utils/excelDownload";
const selectableKeys: Record<TKey, string> = {
productList: "상품 목록",
gamecharacters: "게임 캐릭터들",
customerOrderList: "주문 목록",
};
const dataFieldLabels: Record<TKey, Record<string, string>> = {
productList: {
productName: "상품 이름",
category: "카테고리",
price: "가격",
stock: "남은 개수",
registeredDate: "등록 날짜",
},
gamecharacters: {
name: "이름",
class: "직업",
level: "레벨",
health: "체력",
mana: "마나",
attackPower: "공격력",
defense: "방어력",
creationDate: "생성날짜",
},
customerOrderList: {
customerName: "주문자 이름",
productName: "상품 이름",
quantity: "주문량",
price: "가격",
orderDate: "주문 날짜",
status: "주문 상태",
},
};
interface IData {
productList: IProduct[];
gamecharacters: IGameCharacter[];
customerOrderList: ICustomerOrder[];
}
function App() {
const [isLoading, setIsLoading] = useState<boolean>(false);
const [selectedKey, setSelectedKey] = useState<TKey>("productList");
const [data, setData] = useState<IData>({
productList: cloneDeep(ProductList),
gamecharacters: cloneDeep(GameCharacters),
customerOrderList: cloneDeep(CustomerOrders),
});
const handleChangeSelectedKey = (clickedKey: TKey) => {
setSelectedKey(clickedKey);
};
const downloadExcelFile = () => {
// 1. 로딩 상태 활성화
setIsLoading(true);
// 2. 현재 선택된 데이터의 필드 라벨 가져오기
const selectedLabels = dataFieldLabels[selectedKey];
// 3. 현재 선택된 데이터 가져오기
const selectedData = data[selectedKey];
/* electedData 값 예시: [
{
id: "P1001",
productName: "무선 블루투스 이어폰",
category: "전자기기",
price: 129000,
stock: 50,
registeredDate: "2024-02-20",
}
...
]
*/
// 4. 데이터 가공 (각 행 데이터를 배열로 변환)
const rows: string[][] = selectedData.map((datum) =>
Object.values(datum).slice(1)
);
// 5. 컬럼 헤더 가져오기
const columnHeaders = Object.values(selectedLabels);
// 6. 엑셀 파일 생성
const book = XLSX.utils.book_new(); // 새 엑셀 문서 생성
const column = XLSX.utils.aoa_to_sheet([[...columnHeaders], ...rows]); // 데이터 시트 생성
XLSX.utils.book_append_sheet(book, column); // 문서에 시트 추가
// 7. 파일명 설정
const fileName = `엑셀_다운로드_${new Date().toISOString().slice(0, 10)}`;
// 8. 파일 다운로드 실행
excelDownload({ excelTemplate: book, fileName });
// 9. 로딩 상태 비활성화
setIsLoading(false);
};
return (
<div>
<div>
<div>
{Object.keys(selectableKeys).map((selectableKey) => (
<div key={selectableKey}>
<button
disabled={isLoading}
onClick={(e) => {
e.preventDefault();
handleChangeSelectedKey(selectableKey as TKey);
}}
>
{selectableKeys[selectableKey as keyof typeof selectableKeys]}
</button>
</div>
))}
</div>
<button
disabled={isLoading}
onClick={(e) => {
e.preventDefault();
downloadExcelFile();
}}
>
엑셀 다운로드
</button>
</div>
<div>
<DataTable
selectedFieldLabels={dataFieldLabels[selectedKey]}
selectedData={data[selectedKey]}
/>
</div>
</div>
);
}
export default App;
우선 위 코드에서 엑셀 다운로드 버튼을 클릭하면 dowlloadExcelFile 함수를 통해 엑셀 파일을 다운로드할 수 있다. downloadExcelFile의 로직 순서는 다음과 같다.
- setIsLoading(true) -> 엑셀 다운로드가 실행되면 로딩 상태를 활성화한다.
- dataFieldLabels[selectedKey] → 현재 선택된 데이터셋의 필드 라벨을 가져온다.
- data[selectedKey] → 현재 선택된 데이터를 가져온다.
- reduce를 사용해 각 행의 데이터(row)를 배열로 변환한다.
- Object.values(datum) → 객체에서 값만 추출
- .slice(1); → ID 제거 (객체의 첫 번째 프로퍼티가 id이다.)
- columnHeaders → 컬럼명 배열을 생성한다.
- XLSX.utils.book_new(); → 새로운 엑셀 문서를 생성한다.
- XLSX.utils.aoa_to_sheet([...]) → 엑셀 시트 데이터로 변환한다.
- excelDownload({...}) → 파일 다운로드를 실행
- setIsLoading(false); → 로딩 상태를 비활성화한다.
02-2. 엑셀 파일 생성 및 다운로드 (excelDownload.ts)
import * as FileSaver from "file-saver";
import * as XLSX from "xlsx";
import XlsxPopulate from "xlsx-populate/browser/xlsx-populate";
const fileExtension = ".xlsx";
export const excelDownload = async ({
excelTemplate,
fileName,
}: {
excelTemplate: XLSX.WorkBook;
fileName: string;
}) => {
// 1. 엑셀 파일 Blob 생성
const excelFile = await getExcelFile(excelTemplate);
// 2. 파일 다운로드 실행
FileSaver.saveAs(excelFile, fileName + fileExtension);
};
async function getExcelFile(excelTemplate: XLSX.WorkBook) {
// 1. 엑셀 데이터를 배열(ArrayBuffer)로 변환
const excelBuffer = XLSX.write(excelTemplate, {
bookType: "xlsx",
type: "array",
});
// 2. XlsxPopulate를 사용하여 워크북 변환
const workbook = await XlsxPopulate.fromDataAsync(excelBuffer);
// 3. Blob 타입으로 변환
const excelFile = (await workbook.outputAsync()) as Blob;
return excelFile;
}
- excelTemplate: XLSX.WorkBook 타입의 엑셀 파일
- getExcelFile 함수 실행 → 엑셀 데이터를 Blob으로 변환한다.
- XLSX.write(...) → 엑셀 데이터를 ArrayBuffer로 변환한다.
- XlsxPopulate.fromDataAsync(...) → XlsxPopulate 라이브러리를 사용해 데이터를 변환한다.
- workbook.outputAsync() → 최종적으로 Blob 타입의 엑셀 파일을 생성한다.
- FileSaver.saveAs(...) → 사용자의 브라우저에서 파일 다운로드를 실행한다.
03. 이용된 주요 함수들
03-1. XLSX.utils.book_new()
const book = XLSX.utils.book_new(); // 새 엑셀 문서 생성
console.log(book);
// 출력 결과
{
SheetNames: [],
Sheets: {}
}
- 새로운 엑셀 워크북(Workbook) 객체를 생성하는 함수이다.
- 엑셀 문서의 가장 기본적인 단위
- 이후 XLSX.utils.book_append_sheet()를 사용해 워크북에 시트를 추가할 수 있다.
03-2. XLSX.utils.aoa_to_sheet(data: any[][])
const columnHeaders = ["이름", "나이", "직업"];
const rows = [
["김철수", 25, "개발자"],
["이영희", 30, "디자이너"],
];
const sheet = XLSX.utils.aoa_to_sheet([[...columnHeaders], ...rows]);
console.log(sheet);
// 출력 결과
{
"A1": {
"v": "이름",
"t": "s"
},
"B1": {
"v": "나이",
"t": "s"
},
"C1": {
"v": "직업",
"t": "s"
},
"A2": {
"v": "김철수",
"t": "s"
},
"B2": {
"v": 25,
"t": "n"
},
"C2": {
"v": "개발자",
"t": "s"
},
"A3": {
"v": "이영희",
"t": "s"
},
"B3": {
"v": 30,
"t": "n"
},
"C3": {
"v": "디자이너",
"t": "s"
},
"!ref": "A1:C3"
}
- 배열 데이터를 시트(Sheet)로 변환하는 함수
- AOA(Array of Arrays, 2D 배열) 구조를 받아들여 엑셀의 워크시트 (Worksheet) 객체를 생성
- 첫 번째 배열이 엑셀의 헤더 (Header) 역할을 함
03-3. XLSX.utils.book_append_sheet(workbook, worksheet, sheetName)
const book = XLSX.utils.book_new(); // 워크북 생성
const sheet = XLSX.utils.aoa_to_sheet([
["이름", "나이"],
["김철수", 25],
]);
// sheetName 생략 시 기본값 "Sheet1"
XLSX.utils.book_append_sheet(book, sheet, "직원목록");
- 워크북 (Workbook)에 워크시트 (Worksheet)를 추가하는 함수
- XLSX.utils.book_new()로 생성한 워크북에 새로운 시트를 추가한다.
- 기본적으로 엑셀 문서는 여러 개의 시트를 가질 수 있다.
- sheetName을 지정하면 탭 이름을 설정할 수 있다.
03-4. XLSX.write(workbook, options)
const excelBuffer = XLSX.write(book, { bookType: "xlsx", type: "array" });
// 출력 예시
Uint8Array(256) [137, 80, 78, 71, 13, 10, 26, 10, ...]
- 엑셀 문서를 실제 파일 형식으로 변환하는 함수
- workbook을 바이너리(binary), 배열(array), 문자열(string) 등 다양한 형식으로 변환 가능
- 최종적으로 파일로 변환하여 다운로드할 때 사용한다.
옵션명 | 설명 |
bookType | 변환할 파일 형식 (xlsx, csv, xls, txt 등) |
type | 데이터 변환 방식 (binary, array, string, buffer 등) |
03-5. XlsxPopulate.fromDataAsync(data: ArrayBuffer | Uint8Array)
const excelBuffer = XLSX.write(book, { bookType: "xlsx", type: "array" });
const workbook = await XlsxPopulate.fromDataAsync(excelBuffer);
- 엑셀 데이터를 읽어서 XlsxPopulate의 워크북 객체로 변환한다.
- XLSX.write()를 통해 ArrayBuffer로 변환한 엑셀 데이터 또는 Uint8Array 형태의 엑셀 파일 데이터를 받아서 워크북(Workbook) 객체를 생성한다.
- 기본 XLSX 구조를 유지하면서, 이후 특정 셀 스타일을 변경하거나 데이터 추가가 가능하다.
- 이후 스타일 적용, 데이터 수정 등의 작업이 가능해짐
03-6. workbook.outputAsync()
const blob = await workbook.outputAsync();
console.log(blob); // Blob 타입 데이터
// 출력 예시
Blob { size: 32876, type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }
- XlsxPopulate 워크북을 Blob(Binary Large Object) 타입으로 변환
- 변환된 Blob 데이터를 활용해 파일 다운로드 또는 클라이언트 측에서 파일 저장 가능
- Blob은 바이너리 데이터(Excel 파일)를 담을 수 있는 JavaScript 객체
03-7. FileSaver.saveAs(excelFile, fileName + fileExtension);
- Blob 데이터를 실제 파일로 변환하고, 다운로드 창을 띄움
- fileName + fileExtension을 파일 이름으로 설정
03-8. 표로 정리
함수명 | 역할 |
XLSX.utils.books_new() | 새로운 엑셀 문서 생성 |
XLSX.utils.aoa_to_sheet() | 2D 배열을 엑셀 시트로 변환 |
XLSX.utils.book_append_sheet() | 엑셀 문서에 시트 추가 |
XLSX.write() | 엑셀 문서를 binary 또는 array로 변환 |
XlsxPopulate.fromDataAsync(data) | ArrayBuffer → Workbook 객체 변환 |
workbook.outputAsync() | Workbook → Blob 변환 |
04. 참고 문서
'기록' 카테고리의 다른 글
노마드코더 SQL 마스터클래스 강의 기록 01 (1) | 2025.03.08 |
---|---|
리액트 엑셀 업로드 기능 간단하게 구현해보기 (0) | 2025.02.16 |
Prisma의 skip, take (0) | 2025.02.09 |
Next.js에서 redirect()를 이용할 때 주의할 점, input[disabled]의 FormData 포함 여부 (0) | 2025.02.08 |
React 클래스형 컴포넌트에서 자주 사용되는 생명 주기 메서드 간단 기록 (0) | 2025.02.01 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- async
- 프로그래머스
- NextJS
- 비트마스킹
- C++
- CSS
- themoviedb
- Redux
- 타입스크립트
- react
- 햄버거버튼
- 스택
- 코드스테이츠
- 카카오맵
- typescript
- 리액트
- aws
- 다이나믹프로그래밍
- 넥스트js
- 순열
- 동적계획법
- 알고리즘
- Next.js
- 백준
- SQL
- 완전탐색
- BFS
- 자바스크립트
- 구현
- 브루트포스
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
글 보관함