1편에 이어 오늘은 컴포넌트화하여 재사용성을 높이는 시간입니다.
프로그래밍을 배운 곳은 다양합니다.
누군가는 유명대학 전공출신이기도 하고, 누군가는 부트캠프에서 배우기도 하고, 또 누군가는 혼자 독학으로 공부했을지도 모릅니다.
출신은 모두 다를지라도, 되고싶은 궁극의 개발자는 어쩌면 단순 코드만 짜내는 노동자 코더가 아닌,
🔅 구조를 설계하며 효율적으로 코드들을 관리하는 프로그래머라고 생각합니다.
🔅 효율적인 코드 관리를 위해서라면 항상 재사용성을 염두에 두고 코딩을 하는게 좋습니다.
재사용성이란 단순히 하나의 컴포넌트를 여러군데서 글로벌하게 부를 수 있게 만드는걸 말합니다.
컴포넌트를 제작하면서 바로 글로벌하게 만드는건 어려울 수 있습니다.
단순한 컴포넌트를 두개 만들고, 두개의 공통된 필드를 비교하면서 하나의 글로벌한 컴포넌트를 만들고,
두개의 컴포넌트대신 하나의 글로벌한 컴포넌트로 대체하면서 연습하는 것이 도움이 될거라 생각합니다.
이번에 사용할 데이터는 아래와 같습니다.
데이터가 두개가 될 예정이라 data.js 혹은 data.ts라는 파일을 만들고 해당 파일에 아래의 데이터를 추가하겠습니다.
export const data = [
{
name: "Jack",
age: 30,
region: "USA",
occupation: "Lawyer",
},
{
name: "Emily",
age: 25,
region: "Canada",
occupation: "Engineer",
},
{
name: "Sophia",
age: 42,
region: "UK",
occupation: "Doctor",
},
{
name: "Michael",
age: 35,
region: "Australia",
occupation: "Teacher",
},
{
name: "Emma",
age: 28,
region: "Germany",
occupation: "Designer",
},
];
export const data2 = [
{
name: "Jack",
gender: "남자",
eye: "blue",
},
{
name: "Emily",
gender: "여자",
eye: "brown",
},
{
name: "Sophia",
gender: "여자",
eye: "green",
},
{
name: "Michael",
gender: "남자",
eye: "hazel",
},
{
name: "Emma",
gender: "여자",
eye: "blue",
},
];
그리고 ExcelImporter.js 혹은 ExcelImporter.tsx 파일을 만들어 줍니다.
그리고 데이터를 제외한 모든 코드들을 해당 파일로 옮겨줍니다.
import * as XLSX from "xlsx";
import * as FileSaver from "file-saver";
const ExcelImporter = () => {
const excelFileType =
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8";
const excelFileExtension = ".xlsx";
const excelFileName = "엑셀파일이름";
const excelDownload = async (data) => {
const ws = XLSX.utils.aoa_to_sheet([
[`엑셀데이터`],
[``],
[`이름`, `나이`, `직업`, `국적`],
]);
data.map((v) => {
XLSX.utils.sheet_add_aoa(ws, [[v.name, v.age, v.occupation, v.region]], {
origin: -1,
});
ws["!cols"] = [{ wpx: 100 }, { wpx: 100 }, { wpx: 100 }, { wpx: 100 }];
return false;
});
const wb = {
Sheets: { data: ws },
SheetNames: ["data"],
};
const excelButter = XLSX.write(wb, { bookType: "xlsx", type: "array" });
const excelFile = new Blob([excelButter], { type: excelFileType });
await new Promise((resolve) => setTimeout(resolve, 1000));
FileSaver.saveAs(excelFile, excelFileName + excelFileExtension);
};
const handleDownloadExcel = () => {
excelDownload(data);
};
return <button onClick={handleDownloadExcel}>엑셀 다운 버튼</button>;
};
export default ExcelImporter;
그러면 이제 App.js / App.tsx 파일은 빈 파일이 되었고, ExcelImporter에서 시작하시면 됩니다.
이제 컴포넌트화를 진행할건데, 이렇게 옮기고 나면 위에서부터 한줄씩 보면 됩니다.
가장 먼저 글로벌하게 바뀌어야 할 애는
const excelFileName = "엑셀파일이름";
파일 이름이 내가 원하는 파일 이름으로 글로벌하게 바뀌면 좋겠습니다.
아래처럼 props를 이용해서 파일이름을 다이나믹하게 변경해 줍니다.
const ExcelImporter = (props) => {
const excelFileType =
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8";
const excelFileExtension = ".xlsx";
const excelFileName = props.fileName;
//... 나머지 코드
}
타입스크립트는 interface를 통해 더 편하게 사용 가능합니다.
interface IPropsType {
fileName: string;
}
const ExcelImporter = (props: IPropsType) => {
const excelFileType =
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8";
const excelFileExtension = ".xlsx";
const excelFileName = props.fileName;
//... 나머지 코드
}
위 처럼 바뀌어야 할 내용들을 하나씩 차례대로 바꿔주면 됩니다.
제가 뽑은 바뀌어야 할 나머지 내용들은
1. 엑셀에 추가될 테이블 헤더와 정보
[`엑셀데이터`],
[``],
[`이름`, `나이`, `직업`, `국적`],
2. 엑셀에 추가될 테이블 몸통의 정보와 셀 크기
[v.name, v.age, v.occupation, v.region]
{ wpx: 100 }, { wpx: 100 }, { wpx: 100 }, { wpx: 100 }
3. 탭 이름
SheetNames: ["data"],
4. 버튼안에 사용될 텍스트
<button onClick={handleDownloadExcel}>엑셀 다운 버튼</button>
이렇게 있겠습니다.
해당 내용들을 다 props를 통해 주려고 하는데, 문제가 생겼습니다.
우리는 테이블에 뭐가 들어올지 모르는데, 해당 내용들로 어떻게 다이나믹하게 엑셀 테이블에 집어넣나요?
{
name: "Jack",
age: 30,
region: "USA",
occupation: "Lawyer",
}
우리가 엑셀 테이블을 만들 데이터 입니다.
헤더는 이름, 나이, 국적 이런식으로 추가될 예정이고
바디는 Jack, 30 ... 이런식으로 추가될 예정입니다.
헤더는 매니지먼트 팀과 데이터를 건내주는 서버팀과 분명히 다른 이름을 원할 겁니다.
서버는 object key에 한글 사용을 할리가 없고, 아마 위처럼 name, age 이렇게 주겠지만,
매니지먼트팀은 해당 내용들을 한글로 보여주고 싶을 겁니다.
그래서 우리는 헤더는 따로 저장된 배열로 보여주고, 바디에 들어갈 내용은 Object.values()를 사용해서 데이터에서 꺼내서 보여줄 예정입니다.
파일을 하나만 더 만들게요, data_structure.js / data_structure.tsx 입니다.
그리고 내용을 추가합니다.
export const BasicInfoHeader = [`이름`, `나이`, `국적`, `직업`];
export const BasicInfoCellSize = [
{ wpx: 100 },
{ wpx: 100 },
{ wpx: 100 },
{ wpx: 100 },
];
export const PhysicalInfoHeader = [`이름`, `성별`, `눈 색상`];
export const PhysicalInfoCellSize = [{ wpx: 100 }, { wpx: 50 }, { wpx: 100 }];
셀 크기도 다 다르게 들어갈 확률이 크기 때문에 이렇게 따로 배열을 만들어서 관리를 해주는게 좋다고 생각됩니다.
이제 위의 내용들은 props로 들어올 예정이기 때문에, 하나는 header라 부르고, 다른 하나는 cellSize라 부르기로 했습니다. (데이터도 props로 들어올 예정입니다.)
const excelDownload = async (data) => {
const ws = XLSX.utils.aoa_to_sheet([
[`${props.fileName}`],
[``],
props.header,
]);
props.data.map((v) => {
XLSX.utils.sheet_add_aoa(ws, [[v.name, v.age, v.occupation, v.region]], {
origin: -1,
});
ws["!cols"] = props.cellSize;
return false;
});
//... 나머지 코드
}
다음은
[v.name, v.age, v.occupation, v.region]
이 부분을 변경해야하는데, 생각보다 어렵지 않습니다.
아까 언급했듯이, Object.values()를 사용할 예정인데, 해당 괄호에 Object 형식의 데이터만 넣으면 알아서 value를 뽑아줍니다.
우리는 map을 이용해서 배열의 하나하나 엘리먼트에 접근하는데 엘리먼트를 보면, (console.log(v))
{
name: "Jack",
age: 30,
region: "USA",
occupation: "Lawyer",
}
이미 이렇게 object 형식입니다.
그래서 그냥 이 v를 Object.values() 이 안에 추가해서
Object.values(v)
이렇게 써주면 끝입니다.
이제 탭 이름과 버튼text 내용만 변경해주면 끝이겠네요.
빠르게 props를 통해서 tabName, btnTxt를 추가해줍시다.
탭 이름을 변경하려고 하는데, 다이나믹하게 변경하기 위해서는 미리 공간을 잡고, 아래처럼 추가해 주었습니다.
const wb = {
Sheets: {},
SheetNames: [],
};
wb.Sheets[props.tabName] = ws;
wb.SheetNames.push(props.tabName);
<button onClick={handleDownloadExcel}>{props.btnTxt}</button>
마지막으로 우리는 데이터도 다이나믹하게 줄 예정이니까, 파라미터에서 데이터도 빼줍니다.
const handleDownloadExcel = () => {
excelDownload();
};
이 부분도 이렇게 props로 넘겨줍시다.
이제 나머지는 건들건 없고, 한번 불러보겠습니다.
import ExcelImporter from "./ExcelImporter";
import {
BasicInfoCellSize,
BasicInfoHeader,
PhysicalInfoCellSize,
PhysicalInfoHeader,
} from "./data_structure";
import { data, data2 } from "./data";
const App = () => {
return (
<>
<ExcelImporter
fileName="기본정보표"
header={BasicInfoHeader}
cellSize={BasicInfoCellSize}
tabName="info"
btnTxt="기본 정보 엑셀 추출 버튼"
data={data}
/>
<ExcelImporter
fileName="신체정보표"
header={PhysicalInfoHeader}
cellSize={PhysicalInfoCellSize}
tabName="info"
btnTxt="신체 정보 엑셀 추출 버튼"
data={data2}
/>
</>
);
};
export default App;
파일이름을 보니까 성공한게 확인이 됩니다.
파일을 열어서 나머지 정보도 확인해보니 잘 들어왔군요.
🔅 전체코드 (ExcelImporter.js)
import * as XLSX from "xlsx";
import * as FileSaver from "file-saver";
const ExcelImporter = (props) => {
const excelFileType =
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8";
const excelFileExtension = ".xlsx";
const excelFileName = props.fileName;
const excelDownload = async (data) => {
const wb = {
Sheets: {},
SheetNames: [],
};
const ws = XLSX.utils.aoa_to_sheet([
[`${props.fileName}`],
[``],
props.header,
]);
data.map((v) => {
XLSX.utils.sheet_add_aoa(ws, [Object.values(v)], {
origin: -1,
});
ws["!cols"] = props.cellSize;
return false;
});
wb.Sheets[props.tabName] = ws;
wb.SheetNames.push(props.tabName);
const excelButter = XLSX.write(wb, { bookType: "xlsx", type: "array" });
const excelFile = new Blob([excelButter], { type: excelFileType });
await new Promise((resolve) => setTimeout(resolve, 1000));
FileSaver.saveAs(excelFile, excelFileName + excelFileExtension);
};
const handleDownloadExcel = () => {
excelDownload(props.data);
};
return <button onClick={handleDownloadExcel}>{props.btnTxt}</button>;
};
export default ExcelImporter;
🔅 전체코드 (ExcelImporter.tsx)
import * as XLSX from "xlsx";
import * as FileSaver from "file-saver";
interface IPropsType {
fileName: string;
header: string[];
cellSize: CellType[];
tabName: string;
btnTxt: string;
data: Record<string, string | number>[];
}
interface CellType {
wpx: number;
}
interface Worksheet {
[key: string]: XLSX.WorkSheet;
}
interface Workbook {
Sheets: Worksheet;
SheetNames: string[];
}
const ExcelImporter = (props: IPropsType) => {
const excelFileType =
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8";
const excelFileExtension = ".xlsx";
const excelFileName = props.fileName;
const excelDownload = async () => {
const wb: Workbook = {
Sheets: {},
SheetNames: [],
};
const ws = XLSX.utils.aoa_to_sheet([
[`${props.fileName}`],
[``],
props.header,
]);
props.data.map((v: Record<string, string | number>) => {
XLSX.utils.sheet_add_aoa(ws, [Object.values(v)], {
origin: -1,
});
ws["!cols"] = props.cellSize;
return false;
});
wb.Sheets[props.tabName] = ws;
wb.SheetNames.push(props.tabName);
const excelButter = XLSX.write(wb, { bookType: "xlsx", type: "array" });
const excelFile = new Blob([excelButter], { type: excelFileType });
await new Promise((resolve) => setTimeout(resolve, 1000));
FileSaver.saveAs(excelFile, excelFileName + excelFileExtension);
};
const handleDownloadExcel = () => {
excelDownload();
};
return <button onClick={handleDownloadExcel}>{props.btnTxt}</button>;
};
export default ExcelImporter;
두번째 목표인 재사용성 가능한 컴포넌트 만들기도 끝이 났습니다.
이렇게 사용할경우, 이 컴포넌트 하나로 모든 데이터 추출을 대체할 수 있겠네요.
그런데 갑자기 매니지먼트 팀에서 이름 / 나이 / 국적 / 직업순서가 아니라,
이름 / 국적 / 직업 / 나이 이렇게 순서를 변경해 달라고 합니다.
저의 코드는 헤더를 정해놓고 사용하니 헤더는 바꾸면 되겠다만..
export const BasicInfoHeader = [`이름`, `나이`, `국적`, `직업`];
들어오는 데이터는 순서대로 value를 뽑아서 추가하다 보니 엑셀 데이터가 바로 터져버립니다.
그런데 또 갑자기 우리 서버팀은 갑자기 데이터를 지네 마음대로 보내기 시작합니다..
export const data = [
{
name: "Jack",
region: "USA",
age: 30,
occupation: "Lawyer",
},
{
region: "Canada",
name: "Emily",
age: 25,
occupation: "Engineer",
},
{
age: 42,
region: "UK",
name: "Sophia",
occupation: "Doctor",
},
{
name: "Michael",
age: 35,
region: "Australia",
occupation: "Teacher",
},
{
occupation: "Designer",
name: "Emma",
region: "Germany",
age: 28,
},
];
그렇게 되었더니 갑자기 제 엑셀 컴포넌트가 다시 한번 박살이 나버렸습니다.
마음같아서는 서버팀에다가
데이터 똑바로좀 주시겠어요?,
이거 저 정렬 해야하는데 서버팀에서 정렬좀 다시해주세요!
라고 하고 싶지만, 서버팀이 "알빠노?" 를 시전하면서 🤷♂️ 누웠습니다.
그래서 결국 클라이언트에서 해당 데이터들을 정렬하게 되었습니다.
현업에서 다들 하하호호 웃으면서 타협하면서 얘기할거 같지만,
실제로는 아주 많은 다양한 사람들을 만나서 도를 닦게 됩니다.
그래서 다음시간에는 원래는 한개의 파일에서 여러개의 탭을 사용하는법을 배우려 했지만,
🆘 긴급수정하여 🆘
3. DB팀 혹은 서버팀에게 패배해 sorting 하기
입니다.
네 저의 이야기 입니다.
'React > Library' 카테고리의 다른 글
리액트 엑셀 - 5. 데이터 필드 변경 (xlsx, file-saver) (0) | 2023.08.23 |
---|---|
리액트 엑셀 - 4. 여러 탭 활용하기 (xlsx, file-saver) (0) | 2023.06.29 |
리액트 엑셀 - 3. 정렬하기 (xlsx, file-saver) (0) | 2023.06.27 |
리액트 엑셀 - 1. 라이브러리 사용방법 기초편 (xlsx, file-saver) (2) | 2023.06.24 |