리액트 DB데이터로 메뉴 트리뷰 만들기
- 흔히 볼 수 있는 메뉴의 트리뷰는 대부분 하드코딩 되어있다
- 프로젝트 간, 권한별 메뉴조회등을 위해 DB에서 메뉴를 불러올 시, 트리뷰는 하드코딩이 불가
- DB에서 불러온 데이터에 따라 트리뷰가 바뀔 수 있도록 컴포넌트 제작
- 트리뷰의 디자인은 deni-react-treeview를 사옹
- npm으로 설치해도 좋고, 다른 라이브러리도 대부분 형태가 같기 때문에 원하는 라이브러리 설치
1. 트리뷰의 기본 형태
import React from 'react'
import DeniReactTreeView from 'deni-react-treeview'
const TreeView = () => {
const fruitsAndVegetables = [
{
id: 100,
text: 'Fruits',
children: [
{
id: 101,
text: 'Orange',
isLeaf: true
},
{
id: 102,
text: 'Banana',
isLeaf: true,
children:[
{
id:111,
text:'banana',
isLeaf:true,
}
]
}
]
},
{
id: 200,
text: 'Vegetables',
children: [
{
id: 201,
text: 'Carrot',
isLeaf: true
},
{
id: 202,
text: 'Tomato',
isLeaf: true
}
]
}
];
return <DeniReactTreeView items={ fruitsAndVegetables } />
}
export default TreeView
- 트리뷰에서 보여줄 메뉴 데이터는 JSON객체 형태로 되어있다
- 직접 하드코딩하면 만들기는 매우 쉽지만 우리는 DB에서 데이터를 가져와 제작하기 위해 로직처리가 필요
2. 필요한 파라미터
SELECT
MENU_NO AS "menuNo"
, MENU_NM AS "menuNm"
, UPPER_MENU_NO AS "upperMenuId
, MENU_TREE_NO AS "menuTreeNo"
FROM COMTNMENUINFO
WHERE Menu_NO > 0
ORDER BY MENU_NO
- menu_no : 메뉴 번호
- menu_nm : 메뉴 이름
- upper_menu_no : 상위 메뉴 번호
menu_tree_no : 메뉴의 트리 단계
- 해당 데이터들은 리액트에서 fetch 혹은 axios로 서버와 통신하여 객체 형태로 가져오면 된다
3. 트리뷰 로직처리
import React from 'react'
import DeniReactTreeView from 'deni-react-treeview'
const TreeView = (prop) => {
const allMenu = prop || []; //메뉴배열
const data = []; //로직처리 후 데이터를 담을 배열
let lastTreeNo = 0; //트리 단계 초기화
/** 트리 단계를 메뉴 데이터중 가장 하위 트리단계로 설정 */
allMenu.forEach(function(item, index){
var curTreeNo = item.menuTreeNo;
console.log(curTreeNo);
if(curTreeNo > lastTreeNo){
lastTreeNo = curTreeNo;
};
})
/** 해당 메뉴의 하위 트리노드 존재여부 확인 */
function hasChildTreeNode(parentNode){
for(var i = 0; i<allMenu.length; i++){
if(allMenu[i].upperMenuId == parentNode) return true;
}
return false;
}
/** 로직처리, 임시배열에 담는다 */
var temp=[];
for(var j=lastTreeNo; j>0;j--){
for(var i=0; i<allMenu.length; i++){
if(allMenu[i].menuTreeNo == j){
if(hasChildTreeNode(allMenu[i].menuNo)){
var childTemp = [];
temp.find(function(element){
if(element.key == allMenu[i].menuNo){
childTemp.push(element.value);
}
})
for(var k = 0; k < temp.length; k++) {
if(temp[k].key == allMenu[i].menuNo) {
temp.splice(k, 1);
k--;
console.log('a');
}
}
temp.push({
key:allMenu[i].upperMenuId,
value:{
id: allMenu[i].menuNo,
text: allMenu[i].menuNm,
isLeaf: true,
children:childTemp,
},
})
}else{
temp.push({
key:allMenu[i].upperMenuId,
value:{
id: allMenu[i].menuNo,
text: allMenu[i].menuNm,
isLeaf: true
},
})
}
}
}
}
temp.forEach(function(item){
data.push(item.value);
})
return <DeniReactTreeView items={ data } />
}
export default TreeView
컴포넌트의 시작
const TreeView = (prop) => {
const allMenu = prop || []; //메뉴배열
const data = []; //로직처리 후 데이터를 담을 배열
let lastTreeNo = 0; //트리 단계 초기화
/** 트리 단계를 메뉴 데이터중 가장 하위 트리단계로 설정 */
allMenu.forEach(function(item, index){
var curTreeNo = item.menuTreeNo;
console.log(curTreeNo);
if(curTreeNo > lastTreeNo){
lastTreeNo = curTreeNo;
};
})
- 트리뷰를 보여줄 컴포넌트에서, 메뉴데이터를 TreeView 컴포넌트로 전달해 로직처리
- prop : 메뉴정보들이 담겨있는 데이터
- allMenu : 로직처리에서 prop를 사용하기 위해 초기화해주며, prop가 비어있다면, null이나 undefined가 아닌 빈 배열로 초기화
하위 트리노드 존재여부 확인
function hasChildTreeNode(parentNode){
for(var i = 0; i<allMenu.length; i++){
if(allMenu[i].upperMenuId == parentNode) return true;
}
return false;
}
- 데이터중 메뉴번호를 파라미터로 받는다
- allMenu 전체 데이터정보를 순회한다
- 각 데이터들 중 상위메뉴번호와, 파라미터가 일치하면 true를 반환
- 파라미터로 받은 메뉴에게 하위 메뉴가 존재한다는 뜻
로직처리
var temp=[]; //임시배열
//트리단계만큼 순회를 할 것이며, 최하위 트리부터 상위트리 순으로 순회
for(var j=lastTreeNo; j>0;j--){
//전체 메뉴 순회
for(var i=0; i<allMenu.length; i++){
/*
조건 : 메뉴의 트리단계가 현재 가장 상위 for문에서 순회중인 트리단계와 일치한다면
이유 : 가장 하위 단계부터 데이터를 삽입하기 위해서
*/
if(allMenu[i].menuTreeNo == j){
/*
조건 : 만약 하위메뉴가 존재한다면
이유 : 하위메뉴가 존재할 경우, 해당 메뉴의 children 인자로 하위메뉴들을 담아야하기 때문
*/
if(hasChildTreeNode(allMenu[i].menuNo)){
var childTemp = []; //하위 메뉴들을 담을 임시 배열
/*
현재 temp임시배열에 담긴 메뉴들을 순회하며,
키값(상위메뉴번호)가 현재 메뉴의 번호와 일치하는 데이터를 childrenTemp 배열에 담는다
트리단계가 가장 하위인 데이터들부터 순회중이기 때문에
트리단계가 상위로 올라갈 수록, 미리 담겨진 하위메뉴들의 묶음을 가져올 것이다
*/
temp.find(function(element){
if(element.key == allMenu[i].menuNo){
childTemp.push(element.value);
}
})
/*
temp배열에서 하위 메뉴들을 꺼내 childrenTemp에 담았다면
temp배열에서는 해당 메뉴들을 삭제한다.
temp에 있던 낱개 메뉴들이
childrenTemp에 하나의 묶음으로 들어가기 위해
*/
for(var k = 0; k < temp.length; k++) {
if(temp[k].key == allMenu[i].menuNo) {
temp.splice(k, 1);
k--;
console.log('a');
}
}
/*
현재 순회에서 담을 메뉴정보와,
해당 메뉴의 하위메뉴들을 담은 childrenTemp를
하나의 묶음으로 temp 배열에 담는다.
이후 다음 순회에서 이와같은 로직처리를 반복하기 위해
key값에 해당 메뉴의 상위메뉴번호를 넣어준다
*/
temp.push({
key:allMenu[i].upperMenuId,
value:{
id: allMenu[i].menuNo,
text: allMenu[i].menuNm,
isLeaf: true,
children:childTemp,
},
})
}else{
//하위메뉴가 없다면 그대로 담는다
temp.push({
key:allMenu[i].upperMenuId,
value:{
id: allMenu[i].menuNo,
text: allMenu[i].menuNm,
isLeaf: true
},
})
}
}
}
}
- 대부분의 설명은 위 코드블럭에 작성
- 대략적인 전체 흐름
- 가장 하위 트리단계부터 시작
- 해당 트리단계의 메뉴들을 삽입할 예정
- 만약 하위메뉴가 있다면
- 하위메뉴들을 가져와 해당 메뉴에 children으로 넣어줌
- 하위메뉴정보가 삽입된 메뉴를 temp에 삽입
- 만약 하위메뉴가 없다면
- 메뉴정보를 temp에 삽입
- 가장 하위 트리단계부터, temp에서 children을 가져와 세트로 만들어 다시 temp에 담는 과정을 반복하여 전체적인 트리 구조를 만든다