본문 바로가기

[JS] 재귀를 이용하여 Tree UI 구현하기

[JS] 재귀를 이용하여 Tree UI 구현하기

 

재귀에 대한 나의 인상을 무의식 속 의성어로 표현한다면 풍덩!! 풍덩!! 풍덩!! 퐁당 퐁당 퐁..

 

 


 

 Tree UI

 

  •  Tree UI는 화면을 구성할 때 재귀를 사용하는 가장 대표적인 예시이다.

 

 

 

 

만들고자 하는 간단한 HTML DOM 구조
(예: 음료 -> 콜드브루 -> 나이트로 콜드브루)
   <ul id="root">
      <li>
        <input type="checkbox" checked />
        <span>음료</span>
        <ul>
          <li>
            <input type="checkbox" />
            <span>콜드브루</span>
            <ul>
              <li>나이트로 콜드 브루</li>
              <li>돌체 콜드 브루</li>
              <li>제주 비자림 콜드 브루</li>
              <li>콜드 브루</li>
            </ul>
          </li>            
        </ul>            
      </li>            
    </ul>

 

메뉴 데이터 (길어서 생략)
더보기
const menu = [
  {
    type: 'group',
    name: '음료',
    children: [
      {
        type: 'group',
        name: '콜드 브루',
        children: [
          { type: 'item', name: '나이트로 콜드 브루' },
          { type: 'item', name: '돌체 콜드 브루' },
          { type: 'item', name: '제주 비자림 콜드 브루' },
          { type: 'item', name: '콜드 브루' },
        ],
      },
      {
        type: 'group',
        name: '프라푸치노',
        children: [
          { type: 'item', name: '애플 쿠키 크림 프라푸치노' },
          { type: 'item', name: '더블 에스프레소 칩 프라푸치노' },
          { type: 'item', name: '모카 프라푸치노' },
          { type: 'item', name: '피스타치오 크림 프라푸치노' },
        ],
      },
      {
        type: 'group',
        name: '블렌디드',
        children: [
          { type: 'item', name: '망고 바나나 블렌디드' },
          { type: 'item', name: '딸기 요거트 블렌디드' },
          { type: 'item', name: '자몽 셔벗 블렌디드' },
          { type: 'item', name: '피치 & 레몬 블렌디드' },
        ],
      },
      {
        type: 'group',
        name: '티',
        children: [
          { type: 'item', name: '라임 패션 티' },
          { type: 'item', name: '민트 블렌드 티' },
          { type: 'item', name: '아이스 유스베리 티' },
          { type: 'item', name: '아이스 캐모마일 블렌드 티' },
        ],
      },
      {
        type: 'group',
        name: '주스',
        children: [
          { type: 'item', name: '한방에 쭉 감당' },
          { type: 'item', name: '파이팅 청귤' },
          { type: 'item', name: '딸기주스' },
          { type: 'item', name: '도와주 흑흑' },
        ],
      },
    ],
  },
  {
    type: 'group',
    name: '음식',
    children: [
      {
        type: 'group',
        name: '빵',
        children: [
          { type: 'item', name: '트러플 미니 스콘' },
          { type: 'item', name: '보늬밤 몽블랑 데니쉬' },
          { type: 'item', name: '고소한 치즈 베이글' },
          { type: 'item', name: '미니 클래식 스콘' },
        ],
      },
      {
        type: 'group',
        name: '케이크',
        children: [
          { type: 'item', name: '밀당 에그 타르트' },
          { type: 'item', name: '마스카포네 티라미수 케이크' },
          { type: 'item', name: '블루베리 쿠키 치즈 케이크' },
          { type: 'item', name: '부드러운 생크림 카스텔라' },
        ],
      },
      {
        type: 'group',
        name: '샌드위치',
        children: [
          { type: 'item', name: '애플 까망베르 샌드위치' },
          { type: 'item', name: '트리플 머쉬룸 치즈 샌드위치' },
          { type: 'item', name: '로스트 치킨 샐러드 밀 박스' },
          { type: 'item', name: 'B.E.L.T 샌드위치' },
        ],
      },
      {
        type: 'group',
        name: '과일',
        children: [
          { type: 'item', name: '하루 한 컵 RED' },
          { type: 'item', name: '한라봉 가득 핸디 젤리' },
        ],
      },
      {
        type: 'group',
        name: '스낵',
        children: [
          { type: 'item', name: '리저브 초콜릿 세트' },
          { type: 'item', name: '로스티드 아몬드 앤 초콜릿' },
          { type: 'item', name: '마카롱' },
          { type: 'item', name: '자일리톨 캔디 크리스탈 민트' },
        ],
      },
      {
        type: 'group',
        name: '아이스크림',
        children: [
          { type: 'item', name: '자바 칩 유기농 바닐라 아이스크림' },
          { type: 'item', name: '넛츠 초콜릿 아포가토' },
          { type: 'item', name: '바닐라 아포가토' },
        ],
      },
    ],
  },
  {
    type: 'group',
    name: '굿즈',
    children: [
      {
        type: 'group',
        name: '머그',
        children: [
          { type: 'item', name: '우리 한글 블랙 머그 473ml' },
          { type: 'item', name: '서울 투어 머그 355ml' },
          { type: 'item', name: '스타벅스 1호점 머그 400ml' },
          { type: 'item', name: '서울 제주 데이머그 세트' },
        ],
      },
      {
        type: 'group',
        name: '텀블러',
        children: [
          { type: 'item', name: 'SS 부산 투어 텀블러 355ml' },
          { type: 'item', name: 'SS 블랙 헤리티지 오드리 텀블러 355ml' },
          { type: 'item', name: 'SS 에치드 실버 텀블러 473ml' },
        ],
      },
      {
        type: 'group',
        name: '악세사리',
        children: [
          { type: 'item', name: '리저브 오렌지 카드 홀더' },
          { type: 'item', name: '스타벅스 1호점 에코백' },
          { type: 'item', name: '스타벅스 1호점 랩탑 파우치' },
        ],
      },
    ],
  },
  {
    type: 'group',
    name: '카드',
    children: [
      { type: 'item', name: '10000원권' },
      { type: 'item', name: '30000원권' },
      { type: 'item', name: '50000원권' },
      { type: 'item', name: '100000원권' },
    ],
  },
];

 

만들고자 하는 간단한 DOM 구조
(예: 음료 -> 콜드브루 -> 나이트로 콜드브루)

 

재귀 구조가 눈에 보인다.

 


 

코드

 

createTreeView
// 🟡 base case 예시 : menu = [{ name: '콜드 브루', 
//                            children : [{type: 'item', name: '나이트로 콜드 브루'}, 
//                                        {type: 'item', name: '제주 비자림 콜드 브루'}]}]

const root = document.getElementById('root');         // <ul id="root">

function createTreeView(menu, currentNode) {

  menu.forEach(item => {                              // item(콜드 브루) -> {name: '콜드 브루', children: [{..},{..}]}
    const listItem = document.createElement('li');    // li를 만든다.
    
    if (item.children) {                              // item.children 있으면 (있음)
      const input = document.createElement('input');  // input checkbox 만들고
      input.type = 'checkbox';
      
      const span = document.createElement('span');    // span 태그 만들어서
      span.textContent = item.name;                   // '콜드 브루' 넣어줌

      const subList = document.createElement('ul');   // children을 위한 ul 만들어서
      
      listItem.append(input, span, subList);          // li에 input, span, ul 넣음

      createTreeView(item.children, subList);        // 그뒤에 📌 재귀호출(2)
    } else {                                         // (2) item.children 없으면 (나이트로 콜드브루...)
      listItem.textContent = item.name;              // (2) li에 item.name 텍스트 삽입 ('콜드 브루')
    }

    currentNode.appendChild(listItem);               // root에 listItem 넣기   // (2) subList에 li 넣기
  });
}


createTreeView(menu, root);

 

map을 사용
const root = document.getElementById('root');
function createTreeView(menu, currentNode) {

  menu.map(item => {
    const listitem = document.createElement("li")
    currentNode.append(listElements)             // 미리 넣어준다.

    if (item.type === 'item') { 
      listitem.textContent = item.name;
   
    } else {  
      const input = document.createElement('input')
      input.setAttribute('type', 'checkbox');   // setAttribute 써줌.
      
      const span = document.createElement('span');
      span.textContent = item.name;

      const subList = document.createElement('ul')
      listitem.append(input, span, ul);
      
      createTreeView(item.children, subList)
    }
  })
}

createTreeView(menu, root);

 

참고 : 레퍼런스 코드 (reduce 사용)
// declarative solution

function createTreeView(menu, currentNode) {

  const appendCollectionsInfo = function (currentNode, collections) {
    const li = document.createElement('li');
    const checkbox = document.createElement('input');
    checkbox.type = 'checkbox';
    const isItem = collections.type === 'item' ? true : false;
    if (isItem) {
      li.append(collections.name);
    } else {
      const groupName = document.createElement('span');
      groupName.textContent = collections.name;
      li.append(checkbox, groupName);
    }
    const haveChildren = Boolean(collections.children);
    const children = collections.children;
    if (haveChildren) {
      const ul = document.createElement('ul');
      createTreeView(children, ul);
      li.append(ul);
    }
    currentNode.append(li);
    return currentNode;
  };
  menu.reduce(appendCollectionsInfo, currentNode);
}

 

구현 결과

 

728x90

'FE > JavaScript' 카테고리의 다른 글

[JS] 꼬리재귀  (0) 2023.04.27
[JS] 하노이의 탑  (0) 2023.04.20
[JS] JSON.stringify 재귀를 이용하여 직접 구현하기  (0) 2023.04.12
[JS] JSON 개요 (JavaScript Object Notation)  (0) 2023.04.12
[JS] 재귀  (0) 2023.04.11
⬆︎