리액트에서 카테고리를 구현해 보았다.

octofox·2022년 12월 19일
0

하나의 카테고리에는 또 다른 카테고리 정보가 들어있다.
카테고리 마다 그 깊이가 다르다. 어떻게 이 정보들을 유연하게 표현할 수 있을까?

class Category {
	cateNo: number;
	name: string;
	subCate: [] | Category[];
}

구조를 간단하게 클래로 표현해 보았다.
보면 정보가 재귀의 형태를 취하고 있다. subCate는 다시 Category가 된다.

재귀라는 옵션이 머리에 없던 처음에는 카테고리 트리의 마지막 깊이를 확인하고 그 만큼의 useState를 사용하였다. 1번째를 고르는, 2번째, 3번째 ... 이렇게 해서 5개 까지 만들고 컴포넌트도 5개를 정적으로 만들어서 조회 해야할 state가 너무나 많았다. 그리고 다른 더 깊은 카테고리와는 호환되지 않는 정적 컴포넌트들은 재사용되기 어려웠다.
쿠팡, 11번가, 아마존, 알리익스프레스 전부 다른 카테고리들을 가지고 있다.
이것들의 레벨은 물론 다 다르다.

그래서 재귀함수를 사용한 컴포넌트를 만들었다.

컴포넌트들의 종류는 이렇다.
1. 카테고리 컨테이너
2. 카테고리 재귀 함수
3. 카테고리 박스
4. 카테고리 리스트 아이템

트리의 특정 노드를 가리킬 수 있는 효과적인 구조가 무엇일까 생각했다.
트리 노드의 위치는 레벨과 부모 cateNo로 결정된다.
그래서 배열에 cateNo를 모은 형태로 노드를 가리키기로 했다.
배열의 길이는 트리의 레벨이 된다.
배열의 cateNo들은 그 부모들이 누군지 알려준다.

이 정보들을 재귀함수에 넘겨주어서 카테고리 박스를 그리도록 했다.

재귀함수는 이렇게 생겼다.

import CategoryBox from './CategoryBox'

type CategoryRecursionProps = {
  categoryList: any[]
  treePath: any[]
  setTreePath: any
  level: number
}

const CategoryRecursion = ({
  categoryList,
  treePath,
  setTreePath,
  level,
}: CategoryRecursionProps) => {

  function descendCateIndex(categoryList: any[], cateNo: number) {
    return categoryList.findIndex((cate) => cate.cateNo == cateNo)
  }

  return (
    <>
      <CategoryBox
        key={level}
        level={level}
        categoryList={categoryList}
        setTreePath={setTreePath}
      />
      {level < treePath.length ? (
        <CategoryRecursion
          categoryList={
            categoryList[descendCateIndex(categoryList, treePath[level])]
              .subCate
          }
          treePath={treePath}
          setTreePath={setTreePath}
          level={level + 1}
        />
      ) : null}
    </>
  )
}

export default CategoryRecursion

특정 컴포넌트를 반복하기 위해 구조적인 컴포넌트로 CategoryRecursion을 만들었다.
CategoryRecursion은 CategoryBox를 재귀한다.
재귀해서 들어갈 때 레벨은 당연히 1씩 증가한다.
표현되는 카테고리의 레벨은 cateNo로 구성된 배열의 길이에 의해서 제한된다.

import { Col } from 'react-bootstrap'
import CategoryItem from './CategoryItem'

interface CategoryBoxProps {
  categoryList: any[]
  setTreePath: any
  level: number
}

const CategoryBox = ({
  categoryList,
  setTreePath,
  level,
}: CategoryBoxProps) => {
  return categoryList.length != 0 ? (
    <Col>
      {categoryList.map((category, i) => (
        <CategoryItem
          key={i}
          level={level}
          category={category}
          setTreePath={setTreePath}
        />
      ))}
    </Col>
  ) : null
}

export default CategoryBox

카테고리 아이템은 이렇다.

import { ListGroup } from 'react-bootstrap'

interface CategoryItemProps {
  category: any
  setTreePath: any
  level: number
}

const CategoryItem = ({ category, setTreePath, level }: CategoryItemProps) => {
  return (
    <ListGroup.Item
      onClick={() => {
        setTreePath((prev: any[]) => {
          if (prev.length > level) {
            return [...prev.slice(0, level), category.cateNo]
          } else {
            return [...prev, category.cateNo]
          }
        })
      }}
    >
      {category.name}
    </ListGroup.Item>
  )
}

export default CategoryItem

선택된 아이템 노드의 레벨이 현재 표현되고 있는 마지막 레벨보다 작다면 그 위치의 카테고리 정보를 배열에서 제거한다.
반대로 선택된 아이템 노드의 레벨이 현재 표현되고 있는 마지막 레벨과 같다면 배열에 cateNo를 추가한다.

profile
개발자라고 우기는 노답 소년

0개의 댓글