image

ExcelJS 다운로드 & 엑셀 파일 업로드

태그
JavascriptReact
상세설명ExcelJS활용한 엑셀 다운로드 & 엑셀 파일 업로드
작성일자2023.11.26

프로젝트를 진행하면서 엑셀 다운로드 작업 시 자주 사용하는 exceljs 대해 정리했다.

👉 선택 이유

image

npm trends를 보면 지난 1간 라이브러리 다운로드 횟수입니다. xlsx 다운로드 수가 더 많지만 ExcelJS를 선택한 이유는 다음과 같습니다.

exceljs

  • 스타일링과 서식 지정 : 세밀한 스타일링이 필요할 때 유리 ( 다양한 서식 기능, 차트, 이미지, 다양한 셀 유형을 지원 )
  • 호환성과 데이터 처리 : Excel 파일 => JSON 형식, JSON 데이터 => Excel로 변환하는 기능이 있고 세밀한 데이터 조작, 복잡한 데이터 처리 요구 사항 가능
  • 파일 포맷 : xlsx, csv
  • 스트림 지원 : 복잡한 스트림 처리 작업을 더 간편하게 할 수 있음
  • xlsx

  • 스타일링과 서식 지정 : 복잡한 서식이나 스타일링에는 제한 ( 기본적인 읽기, 쓰기 기능을 제공 )
  • 호환성과 데이터 처리 : 대량의 데이터를 신속하게 처리할 수 있지만, 복잡한 데이터 변환이나 고급 조작에는 제약
  • 파일 포맷 : xlsx, csv, html, json 등
  • 스트림 지원 : 레거시 포맷을 지원하기 때문에 스트림 API가 복잡하거나 사용하기 어려움
  • 추가 설명

    * 스트림 읽기(Streaming Read) : 대량의 데이터를 처리할 때 메모리 사용을 최적화하고 성능을 개선하기 위해 사용되는 기법으로 데이터를 한 번에 모두 읽는 대신, 데이터의 일부분씩 순차적으로 읽어 처리합니다. 이 방법은 특히 대규모 파일을 다룰 때 유용합니다

    *레거시 포맷 : 레거시 포맷 (예: xls)은 최신 포맷 (예: xlsx)과 구조가 다르기 때문에, 스트림 API를 사용하여 이러한 파일을 처리할 때 복잡성이 증가할 수 있습니다. 레거시 포맷의 경우, 데이터 구조와 파일 형식의 차이로 인해 스트리밍 읽기와 쓰기 작업이 더 복잡할 수 있습니다.

    결론

    exceljs는 복잡한 Excel 파일의 서식, 스타일링, 고급 기능을 지원하고, JSON과의 호환성으로 데이터 처리 및 조작이 유리하여, 프로젝트의 요구 사항에 적합하다고 판단되었습니다. xlsx는 성능 면에서는 우수하지만, 서식 및 스타일링의 제약과 복잡한 데이터 조작의 어려움으로 인해 exceljs를 선택하게 되었습니다.

    사용법

    exceljs npm

    exceljs 설치

    npm install exceljs

    엑셀 파일 생성 및 다운로드

    import ExcelJS from "exceljs"
    
    export default function ExcelDownload() {
      const data = [
        {
          userID: "12345678",
          userName: "ABC",
          userEmail: "abc@gmail.com",
          createTime: "2023-11-26 10:11:00",
        },
        {
          userID: "46578945",
          userName: "DEF",
          userEmail: "def@gmail.com",
          createTime: "2023-11-26 12:51:10",
        },
        {
          userID: "98745651",
          userName: "가나",
          userEmail: "가나@gmail.com",
          createTime: "2023-11-26 22:30:00",
        },
      ];
    
      const [userDataLoading, setUserDataLoading] = useState(false);
      const handleExcelDownload = async () => {
        setUserDataLoading(true); // 로딩
    
        const workbook = new ExcelJS.Workbook();
        setUserDataLoading(false);  // 로딩
        const worksheet = workbook.addWorksheet("My Sheet");
        worksheet.columns = [
          {
            header: "userID",
            key: "userID",
            width: 20,
            style: { alignment: { horizontal: "left" } },
          },
          {
            header: "닉네임",
            key: "userName",
            width: 20,
            style: { alignment: { horizontal: "left" } },
          },
          {
            header: "이메일",
            key: "userEmail",
            width: 30,
            style: { alignment: { horizontal: "left" } },
          },
          {
            header: "생성날짜",
            key: "createTime",
            width: 30,
            style: { alignment: { horizontal: "left" } },
          },
        ];
        for (let i = 0, j = data.length; i < j; i++) {
          worksheet.addRow({
            userID: data[i].userID,
            userName: data[i].userName,
            userEmail: data[i].userEmail,
            createTime: data[i].createTime.replace(/-/g, "."),
          });
        }
    
        workbook.xlsx.writeBuffer().then((buffer) => {
          const blob = new Blob([buffer], {
            type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
          });
          const excelDonwloadUrl = window.URL.createObjectURL(blob);
          const link = document.createElement("a");
          link.href = excelDonwloadUrl;
    
          link.setAttribute("download", `엑셀내역_${formatDate(new Date())}.xlsx`);
          document.body.appendChild(link);
          link.click();
        });
      };
    
      return (
        <>
          <section>
             <button css={excelBtn} onClick={() => handleExcelDownload()}>
                <Image
                  src={"/excelIcon.png"}
                  alt="excelIcon"
                  width={18}
                  height={18}
                />
                <span>엑셀 다운로드</span>
             </button>
          </section>
          {userDataLoading && (
            <div css={excelDownloadLoading}>
              <span>엑셀 다운로드중입니다.</span>
            </div>
          )}
        </>
      );
    }
    
    const excelBtn = css`
      width: 200px;
      padding: 10px;
      display: flex;
      align-items: center;
      justify-content: center;
      background-color: #207245;
    
      img {
        filter: grayscale(1) brightness(3);
      }
    
      span {
        color: #fff;
        margin-left: 5px;
        font-weight: 500;
      }
    `;
    
    const excelDownloadLoading = css`
      display: flex;
      align-items: center;
      justify-content: center;
      flex-direction: column;
      position: absolute;
      left: 0;
      top: 0;
      width: 100%;
      height: 100%;
      z-index: 20;
      background-color: rgba(0, 0, 0, 0.6);
    
      span {
        margin-top: -15px;
      }
    
      @media (max-width: 1023px) {
        height: 100vh;
      }
    `;

    다운로드 된 엑셀 파일

    image

    엑셀 파일 업로드

    import ExcelJS from "exceljs"
    
    export default function ExcelUpload() {
    	const [selectedFile, setSelectedFile] = useState<File | null>(null);
      const onFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
        if (event.target.files && event.target.files[0]) {
          setSelectedFile(event.target.files[0]);
        }
      };
    
     const onReadExcelClick = async () => {
        //서버로 파일을 업로드할 때 사용
        const formData = new FormData();
        formData.append("file", selectedFile);
        try {
          const response = await fetchCreateListByExcel(url, token, formData)
        } catch (error) {
          console.log(error)
        }
    
        console.log(selectedFile);
    
        // 엑셀 파일 읽기
        if (selectedFile) {
          const workbook = new ExcelJS.Workbook();
          const fileBuffer = await selectedFile.arrayBuffer();
    
          await workbook.xlsx.load(fileBuffer);
    
          workbook.eachSheet((sheet) => {
            sheet.eachRow((row, rowNumber) => {
              console.log(`Row ${rowNumber}: ${row.values}`);
            });
          });
        }
     };
    
     return (
        <>
          <section>
              <div css={excelUploadBtn}>
                <input
                  id="file-input"
                  type="file"
                  style={{ display: "none" }}
                  onChange={onFileChange}
                />
                <label htmlFor="file-input" className="inputLabel">
                  {selectedFile ? (
                    <span style={{ color: "#BCBCBC" }}>{selectedFile.name}</span>
                  ) : (
                    <span style={{ color: "#BCBCBC" }}>
                      등록할 파일을 첨부해주세요
                    </span>
                  )}
                </label>
                <button className="uploadBtn" onClick={() => onReadExcelClick()}>
                  엑셀 업로드
                </button>
              </div>
          </section>
        </>
      );
    }
    
    const excelUploadBtn = css`
      width: 500px;
      padding: 10px;
    
      .inputLabel {
        padding: 10px 50px;
        border: 1px solid #fff;
      }
    
      .uploadBtn {
        background-color: #207245;
        margin-left: 10px;
        font-weight: 500;
        padding: 13px;
        color: #fff;
      }
    `;

    업로드 된 엑셀 파일

  • 미 업로드
  • image

  • 업로드 시 파일 명 보임
  • image

  • 엑셀 내용
  • image