[React] + dataGrid 트리 구조 테이블에서 합계 구하는 법 (feat. 재귀 함수)
| 1월 | 2월 | 3월 | 4월 | 5월 | ...
---------------------------------------------------------------------
총 비용 | | | | | |
---------------------------------------------------------------------
재료비 | | | | | |
---------------------------------------------------------------------
시멘트 | | | | | |
---------------------------------------------------------------------
철근 | | | | | |
---------------------------------------------------------------------
벽돌 | | | | | |
---------------------------------------------------------------------
운송비 | | | | | |
---------------------------------------------------------------------
용달 | | | | | |
---------------------------------------------------------------------
화물 | | | | | |
---------------------------------------------------------------------
인건비 | | | | | |
---------------------------------------------------------------------
식비 | | | | | |
---------------------------------------------------------------------
안전 장비 | | | | | |
---------------------------------------------------------------------
급여 | | | | | |
---------------------------------------------------------------------
- 개발 환경 -
> JavaScript
> React ( class 형)
> MobX
- 요건 설명 -
위와 같은 테이블이 있다.
테이블은 dataGrid 로 구현했다.
건축하는 데에 드는 비용을 정리해 놓은 테이블이다.
항목은 트리 구조로 이루어져 있다.
<총 비용>은 <재료비>+<운송비>+<인건비>이다.
<재료비>는 다시 <시멘트>+<철근>+<벽돌>이다.
<운송비>는 <용달>+<화물>이다.
<인건비>는 <식비>+<안전 장비>+<급여>이다.
총 3계층으로 이루어져 있다.
값을 직접 입력할 수 있는 셀은 최하위 계층 셀뿐이다.
어떤 셀에 값을 입력한다고 해 보자.
그러면 그 셀과 같은 항목에 속한 3계층 셀들의 합계가 바로 2계층 합계 셀에 입력되어야 한다.
그리고 2계층 합계 셀들의 합계는 1계층 총 합계 셀에 입력되어야 한다.
어떤 셀의 값을 바꾸든 상위 합계 셀들에 모두 반영되어야 한다.
- 설계 -
일단 이벤트 핸들러 함수를 하나 만든다.
이 함수는 cell 에 값을 입력하다가 focus out 될 때 실행된다.
이때 파라미터로 node 정보가 넘어온다.
이로부터 어떤 column 에 값을 입력했는지 key 를 알아낸다.
그리고 함수를 하나 더 만들 거다.
이 함수는 node 와 columnKey 를 파라미터로 받는다.
그러면 파라미터로 받은 node 의 모든 하위 node 들을 들르면서 합계를 구한다.
이렇게 구한 합계를 파라미터로 받은 node 에 입력한다.
단, 합계를 구하려고 들렀던 하위 노드에 다시 하위 노드가 있다면 그 하위 노드를 가지고 똑같은 작업을 먼저 수행한다.
즉, 재귀 함수를 수행하게 된다.
- 함수 설명 -
> dataGrid 에 뿌려지는 값들은 dataGridRows 라는 배열로부터 가져온다.
이 dataGridRows 는 db에서 불러와서 store 에 넣어 둔다.
> setDataGridRows() 는 store 에서 들고 있는 dataGridRows 에 값을 넣어주는 함수다.
rowIndex 와 columnKey 와 value 를 인자로 받는다.
이 인자들은 순서대로, 몇 번째 줄, 몇 번째 칸, 넣을 값이다.
> setDataValue() 는 dataGrid 에서 제공하는 함수다.
rowNode 에 대고 실행할 수 있다.
columnKey 와 value, 즉 몇 번째 칸에 무슨 값을 넣을지를 인자로 받는다.
> rowNode 는 childrenAfterGroup 라는 속성을 가지고 있다.
이 안에는 하위 노드들이 들어 있다.
이렇게 되면 필요한 함수들은 모두 정의가 됐다.
cell 의 값이 바뀌면 이벤트 핸들러가 실행되고,
파라미터로 받은 정보로부터 columnKey 를 구한 뒤,
해당 column 에 있는 합계 값들을 새로 구하도록
rootNode 에서 재귀함수를 실행하면 된다.
// 이벤트 핸들러 함수다.
cell 에 값을 입력하고 편집이 끝날 때 실행된다.
파라미터로 rowNode 정보를 받아온다.
onCellEditingStopped(row) {
// set edited cell value
const columnKey = row.column.colId;
this.props.setDataGridRows(row.node.id, columnKey, row.value);
// start recursive
const rootNode = this.dataGridApi.rowModel.rootNode;
this.setRowNodeSum(rootNode, columnKey);
}
// 하위 노드들을 모두 돌면서 그 합계를 상위 노드에 입력하는 함수다.
rowNode 와 columnKey 를 받아온다.
setRowNodeSum(rowNode, columnKey) {
// escape recursive
if( !(rowNode &&
rowNode.childrenAfterGroup &&
rowNode.childrenAfterGroup.length > 0) ) {
return false;
}
// get total value
let total = 0;
rowNode.childrenAfterGroup.forEach((ele, idx, arr)=>{
this.setRowNodeSum(ele, columnKey);
total = Number(total) + Number(ele.data[columnKey] || "0");
});
// set cell value
if(rowNode.id === "ROOT_NODE_ID") return true; // rootNode 에 setDataValue() 하면 에러남
rowNode.setDataValue(columnKey, total);
this.props.setDataGridRows(rowNode.id, columnKey, total);
}
// 화면상에 출력되는 dataGrid 값들과
store 에서 들고 있는 내부 data 값들이 같도록 유지하는 함수.
// store
@action
setDataGridRows(rowIndex, key, val) {
// null check
if( !(this.dataGridRows && this.dataGridRows.length > 0)) {
console.log('setDataGridRows > there is no dataGridRows in store');
return false;
}
// set data
this.dataGridRows[rowIndex][key] = val;
}
- 주의 사항 -
> setDataGridRows() 가 있는 행들은 모두 지우고 봐도 된다.
설계상 이유로 dataGrid 에 출력되는 값과 store 에서 들고 있는 dataGridRows 의 값을 동기화하느라 실행하는 함수이다.
dataGrid 만 놓고 보자면 이 함수는 없어도 된다.
> 재귀 함수를 쓸 때 가장 중요한 점은 재귀 함수를 적절할 때 탈출하는 것이다.
재귀 함수가 반복해서 실행되다가 언제 탈출해야 하는지 조건문을 잘 설계해야 한다.
> 위에서 setRowNodeSum() 함수 안에 보면 forEach 문을 쓰고 있다.
이 forEach 문 안에는 함수를 넣어줘야 한다.
이때 함수를 넣어줄 때는 화살표 함수를 넣어줘야 한다.
특별한 이유가 있는 게 아니고, 그냥 JavaScript 문법이 그렇다.
forEach 문 안에는 익명 함수든 function(){~~} 기명 함수든 다 넣을 수 있지만 this 를 찾지 못한다.
화살표 함수 ()=>{} 를 넣어 줘야만 forEach 문 안에서 this.setRowNodeSum() 를 다시 실행할 때 함수를 잘 찾아간다.
그렇지 않으면 실행하다가 아래와 같은 에러가 난다.
Uncaught TypeError: Cannot read properties of undefined (reading 'setRowNodeSum')
-221115