탭구현

이윤희·2025년 1월 15일

React/Next 일기

목록 보기
20/52

탭 만들기를 강의 안 듣고 혼자 해봤는데 완전 잘못 생각했다...
탭 3개를 만들어서 각각 눌렀을 때 내용이 보이도록 하는 게 목표였는데,

난 그걸
탭이 보이는 상태탭이 안보이는 상태<로 나누어서 저장해야 된다고 생각해서

let [visible, setVisible] = useState(true);
// let [className, setClassName] = useState('tab-invisible');
let tabClassName = 'tab-invisible'; 

이렇게함
그리고 visible 일때 invisible 일때 CSS 를 이렇게 작성해서
CSS로 조작하려고 했다


.tab-visible{
  height: 100px;
  background-color: red;
  border: 1px solid #000;
  color:white;
}

.tab-invisible{
  display: none;

}

탭은 컴포넌트로 뺐고...

function Tab(props){
  return (
    <Nav.Item>
      <Nav.Link onClick={()=>{
        return  !props.tabVisible ? props.setTabVisible(true) : null
      }} eventKey="link0">버튼{props.index}</Nav.Link>
      <div>내용{props.index}</div>
    </Nav.Item>
  )
}

map으로 하고 조건문도 주고 그랬다 (;;)

<Nav variant="tabs"  defaultActiveKey="link0">
{
	tabs.map((a, i)=>{
	return <Tab className={tabClassName} tabVisible={tabVisible} setTabVisible={setTabVisible} index={i} />
})
  }
{
  tabVisible ? tabClassName = 'tab-visible' : null
}

그랬더니,,, 이상함!!!
뭔가 잘못됨을 느낌...ㅋㅋㅋㅋㅋㅋㅋㅋㅋ 겨우 탭인데 뭔가 너무 복잡해졌다.

+ 지금 생각해보니까 굳이 클래스를 써서 CSS로 하지 않으면 되는 문제였던것 같음... 왜이렇게 복잡하게 한거지
내가 바닐라 자바스크립트에 익숙해서 그런듯... 상태 변경에 좀더 익숙해져야해


GPT

아래는 간단한 구조로 문제를 해결한 코드입니다:

import { useState } from 'react';
import { Nav } from 'react-bootstrap';

function Tabs() {
  const [activeTab, setActiveTab] = useState(0); // 현재 활성화된 탭의 인덱스를 저장

  const tabs = ['탭 1 내용', '탭 2 내용', '탭 3 내용']; // 각 탭의 내용을 배열로 관리

  return (
    <div>
      <Nav variant="tabs" defaultActiveKey="link0">
        {tabs.map((_, index) => (
          <Nav.Item key={index}>
            <Nav.Link
              eventKey={`link${index}`}
              onClick={() => setActiveTab(index)} // 클릭 시 활성화된 탭의 인덱스 업데이트
              className={activeTab === index ? 'tab-active' : 'tab-inactive'}
            >
              버튼 {index + 1}
            </Nav.Link>
          </Nav.Item>
        ))}
      </Nav>

      {/* 활성화된 탭의 내용만 표시 */}
      <div style={{ marginTop: '20px' }}>{tabs[activeTab]}</div>
    </div>
  );
}

export default Tabs;

제안한 코드의 작동 방식

  1. useStateactiveTab 상태 관리

    • useState(0)으로 현재 활성화된 탭의 인덱스를 관리합니다.
      예를 들어, 처음에는 activeTab0이고, "버튼 1"을 나타냅니다.
    • 버튼을 클릭하면 setActiveTab이 호출되어 해당 버튼의 인덱스(index)를 activeTab으로 업데이트합니다.
  2. 탭의 내용을 배열로 관리

    • tabs 배열에는 각 탭의 내용을 문자열로 저장합니다.
    • activeTab 값에 따라 tabs[activeTab]으로 현재 활성화된 탭의 내용을 가져옵니다.
  3. 클릭 이벤트로 상태 변경

    • onClick={() => setActiveTab(index)}는 버튼을 클릭할 때 해당 버튼의 인덱스를 activeTab으로 설정합니다.
  4. 조건부 렌더링

    • 활성화된 탭(activeTab)의 내용만 표시됩니다. 나머지 탭의 내용은 렌더링되지 않습니다.
  5. CSS 클래스 동적 적용

    • className={activeTab === index ? 'tab-active' : 'tab-inactive'}로 활성화된 탭과 비활성화된 탭에 다른 스타일을 적용합니다.

  • 기존 방식으로 할려면...

기존 코드의 문제점

1. 단일 상태로 여러 탭 관리

  • tabVisible 하나로 모든 탭의 상태를 관리하려고 하니, 어떤 탭이 활성화되어야 할지 구분하기 어려워졌습니다.
    문제: 상태가 true면 모든 탭에 영향을 줄 수 있습니다.

2. 내용의 위치

  • 현재 내용은 <div>내용{props.index}</div>로 렌더링되지만, 항상 보여지고 있습니다. 어떤 탭의 내용이 보이는지를 명확히 구분하기 어렵습니다.

3. CSS 클래스 동적 변경 문제

  • tabClassName 값을 직접 변경하면서 조건에 따라 클래스 이름을 바꾸려 했지만, React에서는 상태를 사용하는 것이 더 적합합니다.

  • React는 DOM 조작 대신 상태 관리로 UI를 업데이트하도록 설계되었습니다.

    체크


어떻게 고치면 좋을까요?

1. 여러 탭의 상태 관리 방법

  • 탭마다 별도의 상태를 만들지 않고, 현재 활성화된 탭의 인덱스만 저장합니다.
  • 이렇게 하면 단일 상태로 모든 탭의 활성화 여부를 제어할 수 있습니다.

2. CSS 클래스 적용

  • 작성한 .tab-visible.tab-invisible 클래스를 유지하면서, className을 동적으로 적용해 탭의 가시성을 관리합니다.

3. 코드 개선

  • 아래는 기존 CSS를 활용한 코드입니다:
import { useState } from 'react';
import { Nav } from 'react-bootstrap';

function Tabs() {
  const [activeTab, setActiveTab] = useState(0); // 현재 활성화된 탭의 인덱스 관리

  const tabs = ['탭 1 내용', '탭 2 내용', '탭 3 내용']; // 각 탭의 내용을 배열로 관리

  return (
    <div>
      <Nav variant="tabs" defaultActiveKey="link0">
        {tabs.map((_, index) => (
          <Nav.Item key={index}>
            <Nav.Link
              eventKey={`link${index}`}
              onClick={() => setActiveTab(index)} // 클릭 시 활성화된 탭 변경
            >
              버튼 {index + 1}
            </Nav.Link>
          </Nav.Item>
        ))}
      </Nav>

      {/* 탭 내용 표시 */}
      <div>
        {tabs.map((content, index) => (
          <div
            key={index}
            className={activeTab === index ? 'tab-visible' : 'tab-invisible'}
          >
            {content}
          </div>
        ))}
      </div>
    </div>
  );
}

export default Tabs;

이 코드의 작동 원리

  1. activeTab 상태

    • 현재 활성화된 탭을 나타냅니다. 0은 첫 번째 탭, 1은 두 번째 탭을 의미합니다.
  2. map을 이용한 렌더링

    • tabs.map을 통해 각 탭의 내용과 버튼을 반복적으로 렌더링합니다.
    • 활성화된 탭의 내용만 .tab-visible 클래스를 사용하여 표시되고, 나머지는 .tab-invisible로 숨겨집니다.
  3. CSS 클래스 변경

    • classNameactiveTabindex 비교를 통해 동적으로 설정해 작성한 CSS를 그대로 활용할 수 있습니다.

핵심 요약

  • React에서는 상태를 활용해 동적으로 UI를 업데이트합니다.
  • CSS 클래스는 상태(activeTab)와 연결하여 동적으로 적용하는 것이 효율적입니다.
  • 단순하게 관리하려면 탭 전체를 배열로 저장하고, 활성화된 탭만 표시하는 방식이 좋습니다.




주요 변경 사항

  1. useStateactiveTab 관리

    • activeTab현재 활성화된 탭인덱스를 저장합니다.
    • 탭을 클릭하면 setActiveTab으로 해당 탭의 인덱스를 업데이트합니다.
  2. 탭 내용을 배열로 관리

    • 각 탭의 내용을 배열(tabs)에 담아두고, activeTab을 사용해 현재 활성화된 탭의 내용을 표시합니다.
  3. className 동적 적용

    • activeTabindex를 비교해 활성화된 탭에는 'tab-active', 나머지에는 'tab-inactive' 클래스를 적용합니다. 이를 통해 탭의 스타일을 변경할 수 있습니다.
  4. 구조 단순화

    • 상태와 탭 내용을 상위 컴포넌트에서 관리해 코드가 간결하고 직관적으로 보입니다.

내가 놓친 부분들

className={ } <- 이 안에 삼항연산자를 쓸 수 있다 !!!

당연한건데 생각을 못했음...

 className={activeTab === index ? 'tab-visible' : 'tab-invisible'

map으로 내용 출력

      <div>
        {tabs.map((content, index) => (
          <div
            key={index}
            className={activeTab === index ? 'tab-visible' : 'tab-invisible'}
          >
            {content}
          </div>
    

왜 이생각을 못했지?
map에 content 들 돌때마다 자기자신을 출력하도록 하면 됨...

한꺼번에 일을 다할순 없다

탭 내용을 보여주는 부분과 탭 버튼을 출력하는 부분을 동시에 우겨넣으려고 하니까 혼란이 온 거였다
두개를 나눠서 하면 됨
그리고 좀 차근차근해야 안헷갈린다...

참고해서 다시 만들어볼거임

있던거 다틀렸으니까 그냥 싹다지우고 다시시작함

<Nav variant="tabs"  defaultActiveKey="link0">
  <Nav.Item>
    <Nav.Link eventKey="link0">버튼0</Nav.Link>
  </Nav.Item>
  <Nav.Item>
    <Nav.Link eventKey="link1">버튼1</Nav.Link>
  </Nav.Item>
  <Nav.Item>
    <Nav.Link eventKey="link2">버튼2</Nav.Link>
  </Nav.Item>
</Nav>


<div>내용0</div>
<div>내용1</div>
<div>내용2</div> 

여기부터 제대로 이해를 했어야했는데

<Nav variant="tabs"  defaultActiveKey="link0">
  { 반복문으로 탭 버튼출력하는 곳 }
</Nav>


<div> {탭내용 중에서 몇번째 꺼만 보여주는 곳} </div>

인거임

컴포넌트부터 만들고 그 다음에 변수로 대체할 수 있는 부분들을 찾아봄

리턴 빼먹지마

<Nav variant="tabs"  defaultActiveKey="link0">
  {
    tabContent.map((_, i)=>{
      return <Tab i={i} />
      // 자꾸 빼먹는데 여기서 return 빼먹지마 ㅡㅡ
    })
  }
</Nav>

람다함수쓸때 자꾸 리턴을 빼먹는데
차라리 그럼 중괄호도 생략하던가 자꾸 ()=>{} 이렇게 쓰는게 습관이 됐음 ㅡㅡ

<div>{tabContent[activeTab]}</div>

여기 굳이 중괄호 열 필요 없이 그냥 내용을 꺼내다가 출력하니까 훨씬 간결하고 직관적인 듯.

function Detail(props) {
  let [activeTab, setActiveTab] = useState(0);
  let tabContent = ['탭1내용','탭2내용','탭3내용','탭4내용'];
	// 중략
  	
  return( //중략
    <Nav variant="tabs"  defaultActiveKey="link0">
      {
        tabContent.map((_, i)=>{
          return <Tab i={i} btnIdx={i+1} activeTab={activeTab} setActiveTab={setActiveTab} />
          // 자꾸 빼먹는데 여기서 return 빼먹지마 ㅡㅡ
        })
      }
    </Nav>

    <div>{tabContent[activeTab]}</div>
  )
}

function Tab(props){
  return(
    <Nav.Item>
      <Nav.Link eventKey={`link${props.i}`}
      onClick={()=>{
        props.setActiveTab(props.i);
      }}
      >{'버튼'+ props.btnIdx}</Nav.Link>
    </Nav.Item>
  )
}

ㅜㅜ 드디어 성공함

0개의 댓글