기존 코드 분석
- 2depth 구조
- 상위 카테고리
<li>
의 자식요소로 그에 대응하는 하위 카테고리 메뉴 위치
nav > li > (nav > li ...)
- 상위 카테고리 클릭시 클래스명으로 DOM 제어
-
하위 카테고리
<nav>
에 aria-hidden 속성
보조 기술 사용자에게 혼란을 주지 않기 위해 화면에 보이지 않는 하위 카테고리에{aria hidden: true}
속성 부여- aria hidden
스크린 리더와 같은 보조 기술에 탐색될지 말지를 정하는 속성
기본값은 undefined
화면에 시각적으로 보이지 않는 요소(display : block 혹은 visibillity : hidden) 는 보조기술에도 감지되지 않는다.
화면에 시각적으로 렌더링되지 않지만 명시적으로 숨겨지지 않는 요소는 여전히 접근성 트리에 존재 (overflow: hidden으로 가려지는 요소)
속성을 부여한 요소와 하위요소 모두 접근성 트리에서 제거
- aria hidden
위에 정리한대로 기존 웹 사이트에서는 상위-하위 카티고리 요소가 부모-자식관계로 구조가 짜여있었다. 즉 모든 상하위 카테고리 UI를 모두 렌더한 후에 속성으로 화면에서 보이지 않게 처리한 듯했다. 하지만 사실 상위 카테고리와 하위 카테고리는 각각 데이터만 바뀔 분 같은 UI이기 때문에 리액트에서는 그렇게 요소를 렌더할 필요가 없다고 생각했다. (물론 그렇게 한 이유가 있었겠지만!)
결국 상위 카테고리와 하위 카테고리를 각각 하나의 컴포넌트로, 코드 상에서 형제 관계로 위치시켰다. (상하위 카테고리도 하나의 컴포넌트로 통합하려고 했지만, 생각해보니 둘의 역할이 달랐다. 상: 하위를 연다. 하: 페이지를 이동시킨다.)
구현 과정
로직
- 상위 카테고리 클릭
- 클릭된 상위 카테고리 저장(activeCategory)
- 클릭된 상위 카테고리에 대한 하위 카테고리의 데이터를 렌더 (<—객체 매핑)
데이터 구조
서버에서 받는 데이터의 구조는 다음과 같았다.
사실 원하는 데이터 구조는 ‘상위 카테고리명’에 대한 하위 카테고리 데이터를 담은 객체 형태({상위 카테고리명 : [ ...하위 카테고리 데이터 ]}
)였지만 이런 구조는 백에서 줄때나, 프론트에서 받을 때나 좋지 않은 구조라고 했다.
그래서 이런 데이터를 받은 후 프론트 단에서 데이터를 가공하여 저장하기로 했다.
{
category : [{
"id": 1,
"korean_name": "상위 카테고리명",
"english_name": "category name",
"sub_category": [{
"id": 1,
"korean_name": "하위 카테고리명",
"english_name": "category name",
}, {..}]
}, {..}]
}
작성한 코드
{상위 카테고리명 : [ ...하위 카테고리 ]}
형태로 데이터 가공하기
useEffect(() => {
async function getMenus() {
const menus = await fetch(API.CATEGORY_TEST);
const menu = await menus.json();
setCategories(menu.category);
setSubCategories(
menu.category.reduce(
(sub, c) => ({
...sub,
[c.korean_name]: c.sub_category,
}),
{}
)
);
}
getMenus();
}, []);
- 기능 구현
- Category
상위 메뉴 컴포넌트
카테고리 클릭시, 상위 카테고리 한글명을 인자로 받아activeCategory
state 에 저장하는 함수handleSubNavOn
호출 - SubCategory
하위 메뉴 컴포넌트
activeCategory
state가 업데이트 되면 하위 카테고리 데이터 변경
const [categories, setCategories] = useState([]);
const [subCategories, setSubCategories] = useState({});
const [activeCategory, setActiveCategory] = useState("");
const handleSubNavOn = (name) => {
setActiveCategory(name);
};
return (
<div className="main_nav_box">
<Category
list={categories}
handleClick={handleSubNavOn}
activeItem={activeCategory}
/>
{!!activeCategory && (
<SubCategory
title={activeCategory}
list={subCategories[activeCategory]}
/>
)}
</div>
);