Frontend/React

[React] Tree형 Sidebar 만들기 - 1

오이가지아빠 2022. 4. 8. 16:56

#1. Sidebar 제작 필요성

React로 웹사이트를 만들기로 마음먹고 많은 템플릿과 컴포넌트를 살펴보았지만 딱 이거다 하는게 없었다.

 

구글링을 해봐도 샘플은 강제로 컴포넌트를 병렬로 붙여 놓고 확장하기가 힘들다던지

아니면 제한적인 props를 제공해서 커스터마이징 하기가 힘들다던지..

아니면 그냥 ui가 맘에 안든다던지..(react-pro-sidebar가 제일 괜찮긴 하더라)

 

사실 그냥 남이 만들어 놓은것을 가져다 쓰는게 제일 편하긴 하다. 굳이 편한 방법이 있는데 새로 만드는 것 자체가

시간낭비와 노력낭비지만 만드는 과정속에 배우는게 있지 않을까? 하는 마음에 굳이굳이 직접 만들어보기로 한다.

 

그냥 꼬장이고 오기가 맞는것 같다

 

#2. 기본 골격 생성

Depth가 존재하는 Tree구조의 핵심은 재귀호출이다.

일단 자기 자신과 하위메뉴를 보여주고 그 하위가 있으면 또 같은 골격으로 호출하고 또 반복하고...

이런 도르마무같은 느낌이다

필요한 컴포넌트는 두개, 사이드바 전체를 감쌀 Sb.js, 도르마무로 반복할 SbItem.js

import React from 'react'
import SbItem from './SbItem'

const Sb = ({ items }) => {
  return (
    <div>
      {items.map((subItem, index) =>
        <SbItem item={subItem} key={index} />
      )}
    </div>
  )
}

export default Sb

 

import React from 'react'

const SbItem = ({ item }) => {
  return (
    <div>
      <div>{item.menuNm}</div>
      <div>
        {item.childrens.map((child) => (
          <SbItem item={child} />
        ))}
      </div>
    </div>
  )
}

export default SbItem

위 처럼 간단하게 재귀호출하면서 자식노드를 뿌려주는 컴포넌트를 생성했다.

Sb 에서 list를 넘겨주면 SbItem이 내부에서 재귀호출하면서 트리를 만드는 구조이다.

 

#3. Json 데이터 정제

위처럼 만들어진 컴포넌트에서 처리할 수 있는 데이터는 children 노드를 가지고 있는 트리형 데이터 이다.

그런데 실제 내가 api에서 받은 데이터는 ArrayList 데이터이다.

menuId : 각 메뉴의 유니크한 키 값

windowsUrl : 해당 메뉴의 화면 링크주소

pmenuId : 해당 메뉴의 상위 menuId, 최상위 메뉴는 'ROOT' 라는 pmenuId를 가지고 있다.

 

오라클 db로 connect by prior... 로 뽑아내서 그대로 던진 결과가 이 모양이다.

 

애초에 서버 api에서 트리구조로 예쁘게 뽑아서 주면 제일 좋겠지만, 그냥 내가 쓰기 편한 구조로 재구성 해보자.

 

import React from 'react'
import SbItem from './SbItem'
import menuData from '../sidebar/menuData.json'

const Sb = ({ items }) => {

  const nest = (menuData, menuId = "ROOT", link = 'pmenuId') =>
    menuData.filter(item => item[link] === menuId)
      .map(item => ({ ...item, childrens: nest(menuData, item.menuId) }));
  const tree = nest(menuData);
  
  return (
    <div>
      {tree.map((subItem, index) =>
        <SbItem item={subItem} key={index} />
      )}
    </div>
  )
}

export default Sb

menuData 파일이 서버에서 가져온 ArrayList형 데이터인데,

최상위 menuId"ROOT" 이고 pmenuIdmenuId값을 부모자식 관계로 맺어 자식 데이터들을 childrens에 세팅하는 재귀호출 nest 함수를 만들어 트리구조를 만든다.

 

nest(ArrayList)의 결과인 tree를 로그로 찍어보면 드디어 내가 원하는 트리구조가 만들어 진것을 확인할 수 있다.

childrens 안에 또 childrens 안에 또 childrens

위 데이터를 기준으로 화면을 테스트 해보자.

npm start

모든 메뉴가 리스트형식으로 뿌려지게 되었다!

이제 중요한 기능은 각 메뉴의 depth별로 접었다 폈다 하는 기능을 넣어주기만 하면 될 것 같다.

그 전에 일단 좀 예쁘게 CSS부터 다듬어 보자.

 

#4. CSS 다듬기

당장 보기 힘드니까, 각 메뉴명의 왼쪽에 메뉴의 depth를 표현해주고, 들여쓰기까지 해보자.

import React from 'react'

const SbItem = ({ item, depth = 0 }) => {
  return (
    <div>
      <div style={{paddingLeft: depth * 20}}>[{depth}]{item.menuNm}</div>
      <div>
        {item.childrens.map((child) => (
          <SbItem item={child} depth={depth + 1} />
        ))}
      </div>
    </div>
  )
}

export default SbItem

SbItempropsdepth를 생성하고 기본값을 0으로 준다. 그리고 메뉴명 앞에 [depth] 를 붙이고

왼쪽 paddingdepth * 20으로 설정한다.

트리를 타고 안으로 들어갈 때 마다 depth 값은 + 1이 되고, 깊어질수록 왼쪽 padding도 20씩 늘어나게 된다.

이제야 모두 펼쳐진 메뉴트리처럼 보이기 시작한다.

#5. styled components 설정

인라인 css 대신에 styled-components 를 적용해보겠다. 

npm install styled-components
import styled from "styled-components";
import { Link } from 'react-router-dom';

// 사이드바 전체를 감싸는 div
export const SbContainer = styled.div`
  min-width: 16rem;
  width: auto;
  height: auto;
  min-height: 70vh;
  font-size: 14px;
`

// SbItem에서 하위메뉴들을 묶어줄 div
export const SbSub = styled.div`
  overflow: hidden;
`;

// 메뉴명을 보여줄 div
export const SbTitle = styled.div`
  display: flex;
  align-items: center;
  padding-left: ${props => (props.depth * 20)}px;
  height: 32px;
  &:hover {
    background-color: #f6f6f2;
    cursor: pointer;
    border-right: solid 5px;
  }
`;

// 제일 하위메뉴에서 클릭할 Link 
export const SbLink = styled(Link)`
  color: inherit;
  text-decoration: inherit;
`;

위 처럼 Style을 먹인 div들을 Sb.style.js 파일로 생성하고 Sb.jsSbItem.js에서 Import하여 div들을 대체해준다.

아까 설정했던 depth별로 왼쪽padding을 주는 부분도 이곳에서 설정하고 화면에서는 depth값만 전달해준다.

import React from 'react'
import SbItem from './SbItem'
import menuData from '../sidebar/menuData.json'
import {SbContainer} from './Sb.style'

const Sb = ({ items }) => {

  const nest = (menuData, menuId = "ROOT", link = 'pmenuId') =>
    menuData.filter(item => item[link] === menuId)
      .map(item => ({ ...item, childrens: nest(menuData, item.menuId) }));
  const tree = nest(menuData);

  console.log(tree)

  return (
    <SbContainer>
      {tree.map((subItem, index) =>
        <SbItem item={subItem} key={index} />
      )}
    </SbContainer>
  )
}

export default Sb

 

import React from 'react'
import {SbTitle, SbSub, SbLink} from './Sb.style'

const SbItem = ({ item, depth = 0 }) => {
  return (
    <div>
      <SbTitle depth={depth}>[{depth}]{item.menuNm}</SbTitle>
      <SbSub>
        {item.childrens.map((child) => (
          <SbItem item={child} depth={depth + 1} />
        ))}
      </SbSub>
    </div>
  )
}

export default SbItem

여기까지 적용하고 나면 아래와 같이 좀 더 사이드바 같은 느낌이 난다.

메뉴 레벨 별로 접었다 폈다 하는 기능은 이어지는 글에서 추가해보도록 하겠다.

 

다음글 - [React] Tree형 Sidebar 만들기 - 2 (tistory.com)

 

[React] Tree형 Sidebar 만들기 - 2

이전글 - [React] Tree형 Sidebar 만들기 - 1 (tistory.com) [React] Tree형 Sidebar 만들기 - 1 #1. Sidebar 제작 필요성 React로 웹사이트를 만들기로 마음먹고 많은 템플릿과 컴포넌트를 살펴보았지만 딱 이거..

garve32.tistory.com

 

반응형