티스토리 뷰

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의 로직 순서는 다음과 같다.

  1. setIsLoading(true) -> 엑셀 다운로드가 실행되면 로딩 상태를 활성화한다.
  2. dataFieldLabels[selectedKey] → 현재 선택된 데이터셋의 필드 라벨을 가져온다.
  3. data[selectedKey] → 현재 선택된 데이터를 가져온다.
  4. reduce를 사용해 각 행의 데이터(row)를 배열로 변환한다.
    • Object.values(datum) → 객체에서 값만 추출
    • .slice(1); → ID 제거 (객체의 첫 번째 프로퍼티가 id이다.)
  5. columnHeaders → 컬럼명 배열을 생성한다.
  6. XLSX.utils.book_new(); → 새로운 엑셀 문서를 생성한다.
  7. XLSX.utils.aoa_to_sheet([...]) → 엑셀 시트 데이터로 변환한다.
  8. excelDownload({...}) → 파일 다운로드를 실행
  9. 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;
}

 

 

  1. excelTemplate: XLSX.WorkBook 타입의 엑셀 파일
  2. getExcelFile 함수 실행 → 엑셀 데이터를 Blob으로 변환한다.
  3. XLSX.write(...) → 엑셀 데이터를 ArrayBuffer로 변환한다.
  4. XlsxPopulate.fromDataAsync(...) → XlsxPopulate 라이브러리를 사용해 데이터를 변환한다.
  5. workbook.outputAsync() → 최종적으로 Blob 타입의 엑셀 파일을 생성한다.
  6. 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. 참고 문서

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/05   »
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
글 보관함