🤍Dev : FE/React

React : 아코디언 컴포넌트 기능 구현해보기

jini-dev 2025. 5. 13. 18:38
SMALL


요구사항

카드가 접히고 열리는 것을 구현한다 (아코디언 컴포넌트 구현)
카드가 접혔을 땐 제목 우측에 '+' 를 열렸을 땐 '-' 가 표시 되도록 한다
기본 스타일 코드와 App.js 는 구현되어있다.
// App.js - Accordion 부모 컴포넌트
import Accordion from './test/toggle/Accordion';

function App() {
  return (
    <div style={{ fontSize: '2rem', padding: 30 }}>
      <Accordion title="제목" content="콘텐츠" />
    </div>
  );
}​


라이프사이클을 생각해보면서 구현 해보기 :

React : Lifecycle(라이프 사이클) - 클래스형 컴포넌트 vs 함수형 컴포넌트

 

React : Lifecycle(라이프 사이클) - 클래스형 컴포넌트 vs 함수형 컴포넌트

React : 클래스형 컴포넌트와 훅의 등장 이유 React : 클래스형 컴포넌트와 훅의 등장 이유React : 컴포넌트와 Props, State (+ useState 훅) 화면을 각 요소로 쪼갠 것- 하나의 JSX 를 반환하는 함수 컴포넌트

jini-dev.tistory.com

 

 

풀이

// Accordion 컴포넌트
import React, { useState, useEffect } from 'react';

export default function Accordion({ title, content }) {
  const [isOpen, setIsOpen] = useState(false); // 처음 카드가 접혀있도록 false로 적용했다

// 라이프 사이클 복습을 위해 일부러 body에 event를 추가하고 제거하는 코드를 작성했다.
  useEffect(() => {
    const handleChangeOpenState = () => {
      setIsOpen((prev) => !prev);
    };
    // 마운트 시 클릭이벤트 생성
    document.body.addEventListener('click', handleChangeOpenState);

    return () => { // 언마운트시 클릭이벤트 제거
      document.body.removeEventListener('click', handleChangeOpenState);
    };
  }, [isOpen]);

  return (
    <>
      <div>
        <div
          style={{
            background: '#666',
            color: 'white',
            fontWeight: 'bold',
            padding: 10,
            display: 'flex', 
            justifyContent: 'space-between',
          }}
        >
        {/* flex 값이 주어져서 제목과 '+', '-' 부분은 span 태그로 각각 구현했다 */}
          <span>{title}</span>
          <span> {isOpen ? '-' : '+'}</span>
        </div>
        {isOpen && (
          <div
            style={{
              border: '1px solid #382727',
              padding: 20,
            }}
          >
            {content}
          </div>
        )}
      </div>
    </>
  );
}

 

정답

여기서는 useEffect 를 사용하지 않고 onClick 이벤트를 div에 적용해서 바로 isOpen이 바뀌도록 했다.

import React, { useState } from 'react';

export default function Accordion({ title, content }) {
  const [isOpened, setIsOpened] = useState(false);

  return (
    <>
      <div>
        <div
          style={{
            background: '#666',
            color: 'white',
            fontWeight: 'bold',
            padding: 10,
            display: 'flex',
            justifyContent: 'space-between',
          }}
          onClick={() => {
            setIsOpened(!isOpened); // 또는 setIsOpened((state) => !state);
          }}
        >
          <div>{title}</div>
          <div> {isOpened ? '-' : '+'}</div>
        </div>
        {isOpened && (
          <div
            style={{
              border: '1px solid #382727',
              padding: 20,
            }}
          >
            {content}
          </div>
        )}
      </div>
    </>
  );
}
LIST