React

[React] + dataGrid 트리 구조 테이블에서 합계 구하는 법 (feat. 재귀 함수)

빡새 2022. 12. 12. 08:00
                    |   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

반응형