일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- typescript
- Babel standalone
- jszip
- swagger-typescript-api
- react
- Redux
- code editor
- RouteObserver
- FirebaseAnalytics
- Flutter
- uint16array
- babel
- node
- Completer
- Prism.js
- Excel
- web track
- Game js
- Image Resize typescript
- Raycasting
- KakaoMap
- webrtc
- REST API
- identifierForVender
- androidId
- Three js
- Three-fiber
- userevent_tracker
- uint8array
- methodChannel
- Today
- Total
Never give up
React - excel example(feat. XLSX) 본문
어드민 작업을 할 때, excel import, export기능이 필요한 경우가 많습니다
예를들어, 일괄등록, 엑셀로 추출해서 보기 등등
엣날에 서버쪽에서 작업하는 예제를 올렸었는데, 이번에는 클라이언트에서 구현 및 유틸로 만들어봤습니다
클라이언트에서 했을때 장점으로는
1. 서버쪽에 굳이 excel 라이브러리를 설치 안해도 된다
2. 서버쪽 리소스를 조금이나마 덜 쓸 수 있다
3. 파일업로드 방식이 아닌, json으로 데이터 교환 하기 편하다
정도 있을 수 있을거 같습니다
excelUtil.ts
import * as XLSX from "xlsx";
export const excelUtil = {
/** - generate json from excel file */
excelFiletoJson<T>(file: File) {
return new Promise<T[]>((res, rej) => {
const validExtensions = [".xlsx", ".xls"];
const fileExtension = file.name
.substring(file.name.lastIndexOf("."))
.toLowerCase();
if (!validExtensions.includes(fileExtension)) {
rej("Wrong file format. Only support xls, xlsx");
return;
}
const reader = new FileReader();
reader.onload = () => {
const wb = XLSX.read(reader.result, { type: "array" });
const sheetName = wb.SheetNames[0];
const sheet = wb.Sheets[sheetName];
const sheetData = XLSX.utils.sheet_to_json(sheet) as T[];
res(sheetData);
};
reader.readAsArrayBuffer(file);
});
},
/** - generate and download excel file from json */
jsonToExcel<T>(arr: T[], name: string) {
const ws = XLSX.utils.json_to_sheet(arr);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
XLSX.writeFile(wb, `${name}.xlsx`);
},
/** - generate and get blob file from json */
jsonToExcelBlob<T>(arr: T[]) {
const ws = XLSX.utils.json_to_sheet(arr);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
const binaryWorkbook = XLSX.write(wb, { bookType: "xlsx", type: "binary" });
const buffer = new ArrayBuffer(binaryWorkbook.length);
const view = new Uint8Array(buffer);
for (let i = 0; i < binaryWorkbook.length; i++) {
view[i] = binaryWorkbook.charCodeAt(i) & 0xff;
}
return new Blob([view], { type: "application/octet-stream" });
},
};
만든 함수는 총 3개로
1. excelFile을 json으로 만드는 함수
2. json을 excel로 만드는 함수(다운로드)
3. excel로 만들되 다운로드 하지 않고 blob형태로 만드는 함수
1, 2 번은 제공해주는 함수를 잘(?) 사용하면 되는 부분인거 같습니다
(고로 자세한 설명은 생락햔다.)
3번은 요구사항중에 엑셀파일과 다른파일을 같이 zip으로 묶어주세요 하는 부분이 있어서 만들어봤는데
blob으로 변환하는 과정에서 만들게된 코드인데
.chartCodeAt(i) 를 콜 하는 경우, UTF-16(int, 2bytes)를 반환하게 되고
Uint8Array에 넣을 때, 8bit는 0~255까지만 지원해서 오버플로우가 발생하게 되겠죠
데이터가 꺠지거나, 파일 자체가 문제 생기는 경우가 있습니다
그래서 0xFF 와 and 연산을 하면 최하위 8비트만 보장을 하게되고
최상위 8비트를 버리게 됩니다
그러면 최상위 8비트에 대한 데이터는 손실하게 되는게 아닌가 하는 의문이 생기죠
아스키 코드는 기본적으로 7비트까지만 사용하게 되어 특수한 경우가 아닌 이상 필요가 없습니다
그러면 한국어는 100% 지원할까 해서 테스트를 해보긴 했는데, 제가 사용하는 부분에서는 문제가 없었습니다
그래도 혹시나 해서 필요하다면 별도로 인코딩을 하는방법이 있을 수 있을거 같습니다
const encoder = new TextEncoder();
const utf8Array = encoder.encode(binaryWorkbook);
그럼 왜 굳이 Uint16Array를 사용 안할까 하는 의문이 생기게 되겠죠
기본적으로 엑셀 파일은 바이너리로(8비트)로 이뤄져있습니다
그래서 Uint8Array로 만들었을 때, 별도의 변환과정을 안거치기 위해서 그렇습니다
그래도 혹시 필요할 수 있으니 Uint16Array를 사용한 코드도 준비를 해봤습니다
const utf16Array = new Uint16Array(binaryWorkbook.length);
for (let i = 0; i < binaryWorkbook.length; i++) {
utf16Array[i] = binaryWorkbook.charCodeAt(i);
}
const buffer = utf16Array.buffer;
const uint8Array = new Uint8Array(buffer);
상황에 따라서 맞게 사용하면 될거 같습니다
파일 시스템에 대한 부분은 여기서 넘어가면 될거 같고 추가로 고민해볼 포인트는
array나 object을 저장하고, 불러오고 싶을때는 어떻게 하면 좋을까 하는 고민인데
array인경우, object인경우 컬럼을 어떻게 정의하느냐에 따라 다를거 같습니다
각각 아래와 같이 정의 한다 하면
array : name-index
object : name1.name2
serializeArr<T>(arr: T[]) {
return arr.map((row) => {
let newRow: any = {};
for (const key in row) {
if (Array.isArray(row[key])) {
row[key].forEach((e, i) => {
newRow[`${key}-[${i}]`] = e;
});
} else if (typeof row[key] === "object" && row[key] !== null) {
for (const subKey in row[key]) {
newRow[`${key}.${subKey}`] = row[key][subKey];
}
} else {
newRow[key] = row[key];
}
}
return newRow;
});
}
reconstructArr<T>(arr: T[]) {
return arr.map((row) => {
let newRow: any = {};
const regex = {
list: /^(.*)-\[(\d+)\]$/,
obj: /^(.*)\.(.*)$/,
};
for (const key in row) {
const objMatch = key.match(regex.obj);
const listMatch = key.match(regex.list);
if (objMatch) {
const objName = objMatch[1];
const propName = objMatch[2];
if (newRow[objName] === undefined) {
newRow[objName] = {};
}
newRow[objName][propName] = row[key];
} else if (listMatch) {
const listName = listMatch[1];
const idx = parseInt(listMatch[2]);
if (newRow[listName] === undefined) {
newRow[listName] = [];
}
newRow[listName][idx] = row[key];
} else {
newRow[key] = row[key];
}
}
return newRow;
});
다음 과 같이 serialize, deserialize 해주는 방식을 사용하면 될거 같습니다
다음은 컴포넌트쪽 코드입니다
import React, { ChangeEvent, useRef, useState } from "react";
import Button from "src/component/button";
import Table from "src/component/table";
import { headList } from "src/data/data";
import { PartModel } from "src/model/partModel";
import { excelUtil } from "src/util/excelUtil";
import styles from "./excel.module.css";
const ExcelPage = () => {
const [list, setList] = useState<PartModel[]>([]);
const inputRef = useRef<HTMLInputElement>(null);
const onClickImport = () => {
inputRef.current?.click();
};
const onClickExport = () => {
excelUtil.jsonToExcel(list, "example");
};
const onChangeFile = async (e: ChangeEvent<HTMLInputElement>) => {
const files = e.target.files;
console.log({ files });
if (files === null || files?.length === 0) {
inputRef.current!.value = "";
return;
}
const file = files[0];
if (file.type.includes("sheet")) {
try {
const json = await excelUtil.excelFiletoJson<PartModel>(file);
console.log({ json });
setList(json);
} catch (e) {
alert(e);
}
}
};
return (
<div className={styles.div_main}>
<div className={styles.div_btn}>
<input
ref={inputRef}
className="hidden"
type="file"
onChange={onChangeFile}
/>
<Button onClick={onClickImport}>Import</Button>
<Button onClick={onClickExport}>Export</Button>
</div>
<Table>
<Table.Head list={headList} />
<Table.Body>
{list.length === 0 && (
<tr>
<td colSpan={headList.length}>Empty content</td>
</tr>
)}
{list.map((e, i) => {
return (
<tr key={`tr-${i}`}>
<td>{e.blockName}</td>
<td>{e.portName}</td>
<td>{e.partName}</td>
<td>{e.prefix}</td>
<td>{e.footprint}</td>
</tr>
);
})}
</Table.Body>
</Table>
</div>
);
};
export default ExcelPage;
간단한 코드이니 별도로 깃헙에 올리지는 않았습니다
'WEB' 카테고리의 다른 글
React - zip, unzip(Feat. JSZip) (2) | 2024.11.14 |
---|---|
React - Failed to fetch dynamically imported module (0) | 2024.08.20 |
React - code editor 3(feat. Prism.js) (1) | 2023.12.15 |
React - code editor 2 (0) | 2023.12.15 |
React - code editor 1(feat. babel standalone) (0) | 2023.12.13 |