3편 정렬하기 입니다.
잘못들어온 데이터가 아닌, 기본 구조를 변경해 달라는 요청은 자주 기각되기 때문에, 때로는 알아서 구조에 맞출 필요가 있습니다.
아무리 이 업계에서 오래된 사람이라도 모든 케이스들을 예상하기는 어렵기 때문에,
처음에 짠 구조와 다르게 데이터 필드가 추가되기도 삭제되기도 합니다.
또한 PM에 의해서 구조 전체가 뒤집힐 때도 많습니다. (🔅진짜 많음, 주니어 반박불가🔅)
물론 이런 다이나믹한 케이스들에도 유연하게 대처가 가능해야 합니다.
네 지금이 그런 케이스 라고 해보겠습니다.
먼저 우리 프로젝트 매니저는 정렬 순서를 이번주, 다음주, 다다음주 다 다르게 할거라 합니다.
즉, 정렬은 어차피 바뀔텐데, 매번 데이터를 일일이 작업할 수는 없으니,
아예 배열로 데이터 순서를 넣어두고, 그 순서대로 출력되게 하는건 어떨까요?
data_structure.js / data_struture.ts 파일에
export const BasicInfoOrder = ["name", "region", "occupation", "age"];
이렇게 데이터 순서를 추가합니다.
이러면 만약 다다음주에 담당자가 아 나는 국적을 제일 뒤로 보낼래 라고 말하면,
export const BasicInfoOrder = ["name", "occupation", "age" , "region"];
이렇게 이 배열 순서만 바꿔주면 됩니다.
더 좋은 케이스로는 이렇게 코드내에서 변경이 아니라, 직접 이 변경하는 input과 버튼들을 브라우저에 띄워서
변경하는게 좋겠지만, 이번에는 거기까지는 하지 않겠습니다.
그러면 이제 들어올 순서는 정했고, 이 순서대로 데이터를 정렬해 주면 됩니다.
새로운 props로 이 데이터order를 받습니다.
그리고 아래가 데이터를 정렬하는 코드 입니다. 자세한 내용은 주석으로 추가했습니다.
const sortedData = props.data.map((obj) => {
// 정렬된 object를 담을 변수
const sortedObj = {};
// Object.keys()를 통해, data배열 안의 object들에게서 key를 뽑아냅니다.
// 그리고 만약 props로 dataOrder가 있을경우, sort()를 통해 dataOrder의 순서대로 keys를 정렬합니다.
// 만약 dataOrder가 없으면 그냥 숫자 0을 반환해서 원래 들어온 배열의 순서를 유지합니다.
const sortedKeys = Object.keys(obj).sort((a, b) => {
return props.dataOrder
? props.dataOrder.indexOf(a) - props.dataOrder.indexOf(b)
: 0;
});
// 만약 pm이 안보고 싶은 필드가 있을지도 모르니까, sortedKey안에서 제가 원하는 헤더들만
// 필터링 해서 새로운 key 변수에 담아줍니다.
const desiredKeys = sortedKeys.filter(
(key) => props.dataOrder && props.dataOrder.includes(key)
);
// 모아둔 키 내부를 돌면서, props.data의 object랑 key랑 맞는 데이터를 할당합니다.
desiredKeys.forEach((key) => {
sortedObj[key] = obj[key];
});
// 새롭게 정렬된 object를 return 합니다.
return sortedObj;
});
그다음으로 한 곳만 더 고치면 됩니다.
아래 부분인데요, 어떤 파일은 이렇게 데이터 순서가 필요없이 정리가 잘 된 데이터 일 수도 있습니다.
그럴경우에 위처럼 정렬연산을 하는건 낭비이므로, props에 dataOrder가 들어있으면
정렬된 데이터에서 looping을 하고, 만약 그게 없으면 그냥 들어오는 순수 data에서 loping을 합니다.
(props.dataOrder ? sortedData : props.data)?.map(
(v) => {
XLSX.utils.sheet_add_aoa(ws, [Object.values(v)], {
origin: -1,
});
ws["!cols"] = props.cellSize;
return false;
}
);
테스트를 해보겠습니다.
테스트에 사용될 데이터 입니다.
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,
},
];
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",
},
];
App.js / App.tsx에서 불러주던 컴포넌트에는 한가지 필드를 추가해 주었습니다. (dataOrder)
const App = () => {
return (
<>
<ExcelImporter
fileName="기본정보표"
header={BasicInfoHeader}
cellSize={BasicInfoCellSize}
tabName="data"
btnTxt="기본 정보 엑셀 추출 버튼"
data={data}
dataOrder={BasicInfoOrder} // 추가된 필드
/>
<ExcelImporter
fileName="신체정보표"
header={PhysicalInfoHeader}
cellSize={PhysicalInfoCellSize}
tabName="data"
btnTxt="신체 정보 엑셀 추출 버튼"
data={data2}
/>
</>
);
};
성공입니다. 이제 데이터가 정리가 안되어도, 프론트엔드에서 정리된 데이터를 추출하는게 가능해 졌습니다.
🔅 전체코드 (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 () => {
const wb: Workbook = {
Sheets: {},
SheetNames: [],
};
const sortedData = props.data.map((obj) => {
const sortedObj = {};
const sortedKeys = Object.keys(obj).sort((a, b) => {
return props.dataOrder
? props.dataOrder.indexOf(a) - props.dataOrder.indexOf(b)
: 0;
});
const desiredKeys = sortedKeys.filter(
(key) => props.dataOrder && props.dataOrder.includes(key)
);
desiredKeys.forEach((key) => {
sortedObj[key] = obj[key];
});
return sortedObj;
});
const ws = XLSX.utils.aoa_to_sheet([
[`${props.fileName}`],
[``],
props.header,
]);
(props.dataOrder ? sortedData : props.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();
};
return <button onClick={handleDownloadExcel}>{props.btnTxt}</button>;
};
export default ExcelImporter;
🔅 전체코드 (ts):
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>[];
dataOrder?: string[];
}
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 sortedData = props.data.map((obj: Record<string, string | number>) => {
const sortedObj: Record<string, string | number> = {};
const sortedKeys = Object.keys(obj).sort((a, b) => {
return props.dataOrder
? props.dataOrder.indexOf(a) - props.dataOrder.indexOf(b)
: 0;
});
const desiredKeys = sortedKeys.filter(
(key) => props.dataOrder && props.dataOrder.includes(key)
);
desiredKeys.forEach((key) => {
sortedObj[key] = obj[key];
});
return sortedObj;
});
const ws = XLSX.utils.aoa_to_sheet([
[`${props.fileName}`],
[``],
props.header,
]);
(props.dataOrder ? sortedData : 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;
세번째 목표인 정렬도 끝이 났습니다.
프론트엔드는 많은 사람들과 소통을 하는 포지션에 있습니다.
프론트엔드는 디자이너의 요구사항도 들어줘야하고, PM의 구조설계대로 데이터를 시각화 해야하고, 서버팀과 데이터를 주고 받기도 해야합니다. 더 나아가면, QM이나 테스터 들과도 소통을 해야하는 포지션 입니다.
그만큼 소통의 문제로 고생을 할 확률이 아주 큰 포지션 이지만, 만약 소통이 불가할 경우 (생각보다 많습니다..)
이렇게 유연하게 고생을 1 stack 추가해서 해결 할 수도 있습니다.
다음 시간에는 엑셀다루기 마지막 편 입니다.
PM이 엑셀파일 한개에 기본정보랑 신체정보 모두 담아서 보고싶다고 합니다. 🤷♂️
탭을 여러개 활용하는 멀티 엑셀탭을 활용하는 법에 대해 알아보겠습니다.
'React > Library' 카테고리의 다른 글
리액트 엑셀 - 5. 데이터 필드 변경 (xlsx, file-saver) (0) | 2023.08.23 |
---|---|
리액트 엑셀 - 4. 여러 탭 활용하기 (xlsx, file-saver) (0) | 2023.06.29 |
리액트 엑셀 - 2. 컴포넌트화하여 재사용성 향상 (xlsx, file-saver) (0) | 2023.06.27 |
리액트 엑셀 - 1. 라이브러리 사용방법 기초편 (xlsx, file-saver) (2) | 2023.06.24 |