-
[글또 10기 - 6] 백엔드 개발자가 처음해보는 리액트 프론트엔드 개발 - 사이드바 하위메뉴 기능 분석개발공부/무작정만들어보자 2025. 2. 16. 18:34반응형
하위 메뉴 기능을 개발하였고, 아래와 같이 사이드바 코드가 추가되었다. 이제 코드를 하나씩 분석해보자
// ./component/Sidebar.tsx 'use client'; // 클라이언트 전용 훅(useState)을 사용해야 할 경우 SSR이 아닌 CSR로 동작해야 한다. import { useState } from 'react'; interface MenuItem { label: string; href?: string; subMenu?: MenuItem[]; } const menuItems: MenuItem[] = [ { href: '/pages/about', label: 'onestone' }, { href: '#', label: '스프링부트' }, { label: '스프링배치', href: '#', subMenu: [ { label: '5.0.2', href: '/pages/spring_batch/5.0.2' }, ], }, { href: '#', label: '코틀린' }, { href: '#', label: '그레이들' }, { href: '#', label: '깃헙' }, { href: '#', label: '스프링 카프카' }, { href: '/pages/glossary', label: '용어 사전 및 규칙' }, ]; const Sidebar: React.FC = () => { const [openMenus, setOpenMenus] = useState<{ [key: string]: boolean }>({}); // 상태관리 const toggleMenu = (label: string) => { setOpenMenus((prev) => ({ ...prev, [label]: !prev[label], })); }; const renderMenu = (items: MenuItem[]) => { return ( <ul className={`${styles.menuList}`}> {items.map((item) => ( <li key={item.label} className={styles.menuItem}> {item.subMenu ? ( <> <button onClick={() => toggleMenu(item.label)} className={styles.menuButton} aria-expanded={openMenus[item.label] || false} > {item.label} </button> {openMenus[item.label] && renderMenu(item.subMenu)} </> ) : ( <Link href={item.href || '#'}>{item.label}</Link> )} </li> ))} </ul> ); }; return <div className={styles.sidebar}> {renderMenu(menuItems)} </div>; }; export default Sidebar;
1. 상태 관리 Hook - useState
useState는 리액트 16.8부터 추가된 컴포넌트의 상태를 관리하는 Hook이다. useState는 다음과 같이 사용된다.
const [변수, 변수를_변경할_함수] = useState<제네릭>(기본값);
간단한 예시로 다음과 같이 만들 수 있다.
'use client'; import React, { useState } from 'react'; const Sidebar: React.FC = () => { // const [a, b]: [number, React.Dispatch<React.SetStateAction<number>>] = useState<number>(0); const [a, b] = useState<number>(0); return ( <> <h1>{ a }</h1> <button onClick={() => {b(a + 1);}}>증가</button> </> ); }
a는 숫자를 담을 변수고 b는 숫자를 변경할 함수다. 네이밍을 개발자 마음데로 변경 할 수도 있다는 것을 보여주기 위해 a, b로 표기했다.
아래 코드에서는 openMenus 변수를 setOpenMenus 함수가 변경하는 것으로 이해하면 된다. 변수의 기본값음 객체 {} 이다.
const [openMenus, setOpenMenus] = useState<{ [key: string]: boolean }>({});
변수의 타입은 제네릭으로 선언되어 있다. 타입스크립트의 인덱스 시그니처(Index Signature)를 이해해야 분석할 수 있다.
2. 인덱스 시그니처 - { [key: string]: boolean }
인덱스 시그니처는 { [key : T] : U } 형식이다. 위 코드에서는 { [key: string]: boolean } 로 구현되어 있다.
key는 객체의 키(key)를 의미한다. key: string이므로 key는 문자열 이다. 예를들어, "a", "b", "c" 일 수 있다. value는 객체의 값을 의미한다. [key: string]: boolean이므로 value는 boolean이다. 예를들어 true, false, true이다. 아래 예시 코드를 실행해 보면 이해가 가능할 것이다.
'use client'; const test: { [key: string]: boolean } = { "a": true, "b": false, "c": true }; const Sidebar: React.FC = () => { console.log(`a: ${test["a"]}`); console.log(`b: ${test["b"]}`); console.log(`c: ${test["c"]}`); return <div className={styles.sidebar}></div> }
결과 콘솔 값을 보면, 값이 두 번씩 찍히는데, React.StrictMode가 활성화된 개발 환경에서 리액트가 일부 컴포넌트를 두 번 렌더링하기 때문이다. 개발 모드에서 컴포넌트의 예상치 못한 부작용을 감지하기 위해 추가적으로 렌더링을 두 번 수행하는 기능이므로, 프로덕션 환경에서는 작동하지 않는다.
npm run dev가 아니라 npm run build 후 npm run start를 하면 한 번만 찍히는 것을 확인할 수 있다.
3. 스프레드 연산자(...) - toggleMenu
toggleMenu는 화살표 함수 방식으로 작성되었다. return 값은 없고, openMenu의 값만 변경해준다. 코드를 분석하기 위해서는 스프레드 연산자(...)를 이해해야 한다.
const toggleMenu = (label: string) => { setOpenMenus((prev) => ({ ...prev, [label]: !prev[label], })); };
...prev는 이전 존재했던 객체(openMenus)의 키-값(value)를 유지한다.
[label]: !prev[label], 로 이전 객체(openMenus)의 특정 키를 찾아 값을 반전시켜준다.
예를들어, openMenus의 값 세팅이 다음과 같고, '스프링부트' 메뉴를 클릭한다. 그러면, openMenus의 값을 유지(...prev)하고, 객체의 키인 '스프링부트'를 찾아 값인 false에서 true로 변환한다.
// 값 실행 전 const openMenus = [ 'onestone': false, '스프링부트': false, '스프링배치': false, '코틀린': false ]
// 값 실행 후 const openMenus = [ 'onestone': false, // 값유지 '스프링부트': true, // 변경됨. '스프링배치': false, // 값유지 '코틀린': false // 값유지 ]
반응형'개발공부 > 무작정만들어보자' 카테고리의 다른 글
[글또 10기 - 5] 백엔드 개발자가 처음해보는 리액트 프론트엔드 개발 - 사이드바 하위메뉴 기능 개발 (0) 2025.01.19 [글또 10기 - 4] 백엔드 개발자가 처음해보는 리액트 프론트엔드 개발 - 마크다운 렌더링 페이지 개발 (1) 2024.11.24 [글또 10기 - 3] 백엔드 개발자가 처음해보는 리액트 프론트엔드 개발 - 기본 지식과 사이드바 개발 (1) 2024.11.10 [글또 10기 - 2] 백엔드 개발자가 처음해보는 리액트 프론트엔드 개발 - 기획과 설계 (0) 2024.10.20 [글또 10기 - 1] 백엔드 개발자가 처음해보는 리액트 프론트엔드 개발 (3) 2024.10.01