ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [스터디준비01/티코드(React)] Accordion(아코디언) UI - Single/Multiple configuration
    조각조각/티코드 스터디(React) 2024. 3. 30. 22:32
    ✅ 오늘의 작업 내역
         1. single, multiple 버전 예제 준비하기(100%)
         2. README 작성하기(100%)

     

    완성 화면

    🔍 먼저 짚고 넘어가는 오늘의 깨달음

    동일한 UI인데 실행되는 로직만 다른 경우, prop 조건에 따라 컴포넌트 하나로 해결이 가능하다는 사실을 잊지 말자. 폼도 그렇고, 오늘 준비한 아코디언도 그렇고.. 컴포넌트를 재사용할 수 있는지 항상 먼저 생각해 보기! (single 버전과 multiple 버전을 각각 만들었다가 이거 정말 비효율적이다! 를 느끼고 난 자의 회고록..)

     

    0. 아코디언 UI를 준비한 이유

    요새는 모바일로 웹이나 앱 사용을 많이 하는데, FAQ만 보더라도 아코디언 UI로 이루어져 있는 곳들이 많다. 작은 화면 속에서 빠르게 내용을 확인할 수 있는 게 편리해서 잘 사용하던 기능이었는데 어느 날 문득 "나는 아코디언 UI를 구현할 수 있는가?"에 대해 스스로에게 질문을 던졌을 때 자신 있게 "만들 수 있어!"라고 대답하지 못하는 나를 마주했고... 조만간 스터디 예제로 써봐야겠군이라고 생각한 것이 이유라면 이유랄까요..

    0-1. 시안 만들기

    어떤 스타일을 만들어볼까~ 하고 구글링을 하다가 코드펜에 있던 UI를 참고해서 심플하게 구현해 보기로 결정했다. 그리고 피그마로 샥샥 그려서 UI 스타일 준비도 완료!

    1. 컴포넌트 설계 및 CSS 구조

    1번과 2번은 컴포넌트 설계이고, 2번의 컴포넌트 안에서 그려줄 CSS 구조는 3,4번이다. UI가 동일하기 때문에 prop에 따라 다르게 동작하도록 만들어주는 것이 포인트! 처음에는 single로 열리는 버전만 만드려고 했는데, 유튜브에 재남님이 올려주신 영상을 보다가 multiple 버전도 있었지?라는 것을 깨닫게 되었다. UI도 여러 방식으로 동작할 수 있다는 사실을 항상 염두하자!

     

    2. 공통으로 적용되는 파일 및 코드

    prop에 따라 동작을 다르게 만들어준다고 했지만, 해당 로직은 AccordionList에만 존재하기에 우선 두 버전이 공통으로 사용할 파일에 대한 코드부터 정리하자.

    data.js

    데이터는 id, index, question, answer로 구성했다. 여기서 index는 질문의 순서 번호인데 실제 API로 작업을 할 때는 아마 데이터 구조 자체가 다르지 않을까..라는 생각을 해본다. 지금은 편의상 넣어주기로~!

    const data = [
      {
        id: "d-1",
        index: 1,
        question: "What is JavaScript?",
        answer:
          "JavaScript is a programming language commonly used in web development to add interactivity and dynamic behavior to web pages. It allows developers to create client-side scripts that run within the browser.",
      },
      { ... }
    ]​

     

    Accordion.jsx

    Accordion 파일에서는 사용할 컴포넌트를 선언하고, 알맞은 prop를 넘겨준다. single과 multiple 버전의 공통 prop은 tilte이고, multiple 로직을 실행하려면 multiple prop를 추가로 넘겨주면 된다.

    import AccordionList from "./AccordionList";
    import S from "./index.module.scss";
    
    const Accordion = () => {
      return (
        <article className={S.accordionWrapper}>
          <AccordionList title={"FAQ - single ver."} />
          <AccordionList title={"FAQ - multiple ver."} multiple />
        </article>
      );
    };
    
    export default Accordion;

     

    AccordionItems 컴포넌트

    받아온 data 값을 렌더링 해주는 AccordionItems 컴포넌트는 동일한 prop들을 넘겨받기 때문에 공통으로 사용이 가능하다. 해당 컴포넌트에서 isActive의 boolean 값을 통해 아코디언 섹션의 펼침 유무를 제어할 수 있는데, 이는 isActive의 값에 따라 CSS class에 active 또는 inactvie 값을 넘기는 것으로 가능하다. inactive 클래스가 선택되면 display: none으로 처리되어 화면에서 해당 섹션 요소를 볼 수 없기 때문이다.

    const AccordionItems = ({
      id,
      index,
      question,
      answer,
      handleToggleItem,
      isActive,
    }) => {
      return (
        <>
          <li key={id} className={S.accordionItem}>
            <div
              className={S.accordionHeader}
              onClick={() => {
                handleToggleItem(id);
              }}
            >
              <div>
                <span>{index}. </span>
                <strong>{question}</strong>
              </div>
              <div className={S.accordionIcon}>
                {isActive ? (
                  <svg>
                  	{/* 코드 생략 */}
                  </svg>
                ) : (
                  <svg>
                  	{/* 코드 생략 */}
                  </svg>
                )}
              </div>
            </div>
            <div
              className={`${S.accordionContent} ${
                isActive ? S.active : S.inactive
              }`}
            >
              <p>{answer}</p>
            </div>
          </li>
        </>
      );
    };
    
    export default AccordionList;

     

    3.  Single 버전과 Multiple 버전 구현하기

    아코디언 UI 동작 방식에 따라 생각해 보면, 사용자가 이전에 선택한 id에 해당하는 리스트를 클릭하면 해당 아코디언 섹션의 상태가 변경되어 이전에 열려있었다면 닫히고, 닫혀있었다면 열리는 상태가 되어야 한다. 이를 위해 이전에 선택되어 있는 값을 관리하는 state 하나와 CSS를 제어하기 위한 isActive 값이 각각 존재해야 한다. 리스트는 FAQ라는 상황을 고려하여 순서가 있는 목록인 <ol> 태그를 사용했다.

     

    3-1. Single 버전 구현

    import S from "./index.module.scss";
    import { useState } from "react";
    import data from "./data";
    
    const AccordionList = ({ title }) => {
    
      const [activeId, setActiveId] = useState();
      
      const handleToggleItem = (id) => {
        setActiveId((prevId) => (prevId === id ? null : id));
      };
      
      return (
        <>
          <ol className={S.accordionList}>
            <h1>{title}</h1>
            {data.map((item) => {
              let isActive = activeId === item.id;
              return (
                <AccordionItems
                  {...item}
                  key={item.id}
                  handleToggleItem={handleToggleItem}
                  isActive={isActive}
                />
              );
            })}
          </ol>
        </>
      );
    };

     

    싱글 버전은 토글 방식으로 구동이 된다. 

    • 사용자가 리스트 하나를 선택하면, 이전에 선택되어있던 id값과 현재 선택된 id값을 확인한다.
      • 기존 id값과 현재 id값이 일치하면 null값을 반환하고, 일치하지 않는다면 현재 선택한 id값으로 activeId 상태를 업데이트해준다.
      • 화면에 렌더링 시 선택된 리스트의 섹션 하나만 펼쳐줘야 하기 때문에 isActive 변수에 현재 선택되어 있는 id값인 activeId과 data 목록의 id값이 동일할 경우 true를 반환하여 해당 아코디언 섹션만 펼쳐주도록 처리해 줄 수 있다.

     

    3-2. Multiple 버전 구현

    multiple 버전도 single 버전과 크게 다르지 않다. 다만, 여러 개의 섹션이 동시에 열리기 때문에 배열로 선택한 id값을 관리해 준다.

    import S from "./index.module.scss";
    import { useState } from "react";
    import data from "./data";
    
    const AccordionList = ({ title, multiple }) => {
    
      const [activeArr, setActiveArr] = useState([]);
      
      const handleToggleItem = (id) => {
         if (multiple) {
          setActiveArr((prevId) => {
            if (prevId.includes(id)) {
              return prevId.filter((item) => item !== id);
            } else {
              return [...prevId, id];
            }
          });
        }
      };
      
      return (
        <>
          <ol className={S.accordionList}>
            <h1>{title}</h1>
            {data.map((item) => {
              if (multiple) isActive = activeArr.includes(item.id);
              return (
                <AccordionItems
                  {...item}
                  key={item.id}
                  handleToggleItem={handleToggleItem}
                  isActive={isActive}
                />
              );
            })}
          </ol>
        </>
      );
    };
    • 사용자가 리스트 하나를 선택하면, 이전에 선택되어 있던 id값과 현재 선택된 id값을 확인한다.
      • 기존 id값과 현재 id값이 일치하면 fliter 메서드를 통해 일치하는 id값을 제외한 새로운 배열을 반환하고, 일치하지 않는다면 현재 선택한 id값을 추가하여 activeArr 상태를 업데이트해준다.
      • 화면에 렌더링 시 선택된 리스트 섹션은 모두 펼쳐줘야 하기 때문에 isActive 변수에 현재 선택되어 있는 id값들이 있는 activeArr과 data 목록의 id값이 동일할 경우 true를 반환하여 해당 아코디언 섹션만 펼쳐주도록 처리해 줄 수 있다.

    activeArr 상태값 업데이트

     

     

    Single + Multiple 버전 전체 코드

    import S from "./index.module.scss";
    import { useState } from "react";
    import data from "./data";
    
    const AccordionList = ({ title, multiple }) => {
    
      const [activeId, setActiveId] = useState();
      const [activeArr, setActiveArr] = useState([]);
      
      const handleToggleItem = (id) => {
        setActiveId((prevId) => (prevId === id ? null : id));
        
         if (multiple) {
          setActiveArr((prevId) => {
            if (prevId.includes(id)) {
              return prevId.filter((item) => item !== id);
            } else {
              return [...prevId, id];
            }
          });
        }
      };
      
      return (
        <>
          <ol className={S.accordionList}>
            <h1>{title}</h1>
            {data.map((item) => {
              let isActive = activeId === item.id;
              if (multiple) isActive = activeArr.includes(item.id);
              return (
                <AccordionItems
                  {...item}
                  key={item.id}
                  handleToggleItem={handleToggleItem}
                  isActive={isActive}
                />
              );
            })}
          </ol>
        </>
      );
    };

     

    📚 배운 점

    여전히 처음 시작이 제일 어렵다고 느껴진다. 특히 초기에 컴포넌트를 어떻게 나눌 것이고, 어떤 식으로 로직을 구현할 것인지 설계 단계를 제대로 짚고 넘어가지 않으면 삽질을 여러 번 한다는 것과 불필요한 작업을 여러번 한다는 것을 깨달았다. 무작정 시작하지 말고 어떤 게 필요한지 먼저 파악하고 구상해 보고 작업을 시작하는 습관을 들이자. 

     

     


    참고

    [유튜브] React Dynamic Accordion UI - For Those Who Code(링크)

    [유튜브] Building accordion panel in reactJS with single & multiple configuration - Programming With Prem(링크)

    [유튜브] (UI 요소 직접 만들기) 1-1. 아코디언 컴포넌트 - FE재남(링크)

    [사이트] UI 디자인 참고 - Accordion(링크)

     

    💬 본 포스팅은 공부 기록용으로 정확하지 않은 정보가 존재할 수 있습니다. 발견하신다면 알려주세요!

Designed by Tistory.