TIL #35 - mapping을 이용한 탭 메뉴 구현

Sarang Lee·2021년 2월 28일
0

React

목록 보기
8/9
post-thumbnail

웹사이트를 이용하다보면 탭을 이용해 한 페이지에서 다양한 정보를 보여주는 경우가 많다.

이런 구조는 대체로 메뉴 탭을 누를 때마다 해당 탭 id와 동일한 id를 갖고 있는 컴포넌트를 렌더해주는 형태로 되어 있다. 함께 구현해보자!


1. 컴포넌트 구조

1-1. 먼저 컴포넌트 구조를 짜자!

Product 페이지 컴포넌트 내부에 Tabs(map method로 Tab 컴포넌트 반복 렌더) > Tab가 위치할 수 있도록 한다.
컴포넌트 import, export는 필수!

Product 컴포넌트

import React, { Component } from "react";

import Tabs from "./Components/Tabs/Tabs";
import "./Product.scss";

class Product extends Component {
  render() {
    return (
      <main className="product">
        <Tabs />
        <div className="contentsContainer">
          <content>렌더될 컴포넌트 위치</content>
        </div>
      </main>
    );
  }
}

export default Product;

Tabs 컴포넌트

import React, { Component } from "react";
import Tab from "./Components/Tab/Tab";
import "./Tabs.scss";

class Tabs extends Component {
  render() {
    return (
      <ul className="tabs">
        <Tab />
      </ul>
    );
  }
}

export default Tabs;

Tab 컴포넌트

import React, { Component } from "react";
import "./Tab.scss";

class Tab extends Component {
  render() {
    return (
      <li className=tab>
        {name}
        <span className="countTotal">{count}</span>
      </li>
    );
  }
}

export default Tab;

2. Mock data

2-1. Mock data 만들기

public/data/mockdata.json

{
  "data": {
    "tabs": [
      {
        "id": 1,
        "name": "스토리",
        "count": null
      },
      {
        "id": 2,
        "name": "반환정책",
        "count": 3
      },
      {
        "id": 3,
        "name": "커뮤니티",
        "count": 18
      }
    ]
  }
}

들어오는 데이터에 따라 갯수가 달라지는 탭 컴포넌트를 map 메소드로 반복시켜주려면 우선 목데이터가 필요하다. 이번에 만들 탭은 탭 이름과 함께 탭 내부 컨텐츠 갯수를 보여주고 있기 때문에 name과 count를 데이터로 넣어주었다.

2-2. fetch로 mock data 불러오기

Product 컴포넌트

  constructor() {
    super();
    this.state = {
      tabsData: [],
    };
  }
  
  componentDidMount() {
    this.handleData();
  }

  handleData = () => {
    fetch("/data/mockdata.json")
      .then((res) => res.json())
      .then((res) => {
        this.setState({
          tabsData: res.data.tabs,
        });
      });
  };

위에서 만든 배열 형태의 목데이터를 Product 페이지 컴포넌트에서 fetch(method 'GET' 생략)로 받아온다.

2-3. props를 이용한 데이터 전달

Product 컴포넌트

{tabsData && (
  <>
    <Tabs
      tabsData={tabsData}
    />
    <div className="contentsContainer">
      <content>렌더될 컴포넌트 위치</content>
    </div>
  </>
)}

컴포넌트의 lifecycle을 위해 tabsData가 있는 경우에만 렌더되도록 조건부 렌더링을 설정해준다.
또한 fetch로 받은 데이터를 Tabs와 Tab 컴포넌트에 props로 전달한다.

Tabs 컴포넌트

const { tabsData } = this.props;

return (
  <ul className="tabs">
    {tabsData.map((tab) => (
      <Tab
        key={tab.id}
        id={tab.id}
        name={tab.name}
        count={tab.count}
      />
    ))}
  </ul>

Tabs에서 배열 형태의 데이터에 map method를 이용해 Tab 컴포넌트를 반복해서 출력한 후 props로 데이터를 전달한다.

Tab 컴포넌트

const { id, name, count } = this.props;

return (
  <li
    id={id}
    className="tab"
  >
    {name}
    <span className="count">{count}</span>
  </li>
);

3. 이벤트 함수 설정

3-1. 내부 컴포넌트에 실행될 함수 이름 추가

Tab 컴포넌트

const { id, name, count } = this.props;

return (
  <li
    id={id}
    onClick={() => handleTab(id)}
    className="tab"
  >
    {name}
    <span className="count">{count}</span>
  </li>
);

탭마다 onClick 함수가 실행되어야 하기 때문에 Tab 컴포넌트에서 onClick 이벤트가 일어날 시 실행될 함수명과 함수가 실행될 때 보내줄 값을 넣어준다.
이벤트가 실행되면 어떤 id를 가진 Tab 컴포넌트가 눌린건지 알아야하기 때문에 id를 보내주었다.

3-2. 함수 선언하기

Product 컴포넌트

constructor() {
  super();
  this.state = {
    currentId: 1,
  };
}

handleTab = (id) => {
  this.setState({
    currentId: id,
  });
};

기본으로 보여주어야 할 탭 메뉴의 id를 state에 설정하고, 클릭 이벤트가 생길 시 실행될 함수에 setState로 id를 넣어 클릭 시 컴포넌트가 새로 렌더되도록 한다.

3-3. 함수 props로 전달

Tabs 컴포넌트 & Tab 컴포넌트

<Tabs handleTab={handleTab} />
<Tab handleTab={handleTab} />

함수도 데이터와 마찬가지로 props로 전달해준다.


4. 컴포넌트 객체 렌더 설정

4-1. mapping할 컴포넌트 객체 선언

import Story from "./Components/Story/Story";
import Policy from "./Components/Policy/Policy";
import Community from "./Components/Community/Community";

class 컴포넌트

const MAPPING_TAB = {
  1: <Story />,
  2: <Policy />,
  3: <Community />,
};

출력할 컴포넌트를 import하고 바뀌지 않는 값이기 때문에 class 밖에서 id와 해당 id에 해당하는 컴포넌트를 선언한다.

4-2. 렌더될 영역에 객체의 value 컴포넌트 출력

<div className="contentsContainer">
  <content>{MAPPING_TAB[currentId]}</content>
</div>

결과

Product Component (Page)

import React, { Component } from "react";

import Tabs from "./Components/Tabs/Tabs";
import Story from "./Components/Story/Story";
import Policy from "./Components/Policy/Policy";
import Community from "./Components/Community/Community";
import "./Product.scss";

class Product extends Component {
  constructor() {
    super();
    this.state = {
      currentId: 1,
      tabsData: [],
    };
  }

  componentDidMount() {
    this.handleData();
  }

  handleData = () => {
    fetch("/data/mockdata.json")
      .then((res) => res.json())
      .then((res) => {
        this.setState({
          tabsData: res.data.tabs,
        });
      });
  };

  handleTab = (id) => {
    this.setState({
      currentId: id,
    });
  };

  render() {
    const { handleTab } = this;
    const { currentId, tabsData } = this.state;

    return (
      <main className="product">
        {tabsData && (
          <>
            <Tabs
              handleTab={handleTab}
              tabsData={tabsData}
              currentId={currentId}
            />
            <div className="contentsContainer">
              <content>{MAPPING_TAB[currentId]}</content>
            </div>
          </>
        )}
      </main>
    );
  }
}

const MAPPING_TAB = {
  1: <Story />,
  2: <Policy />,
  3: <Community />,
};

export default Product;

Tabs Component

import React, { Component } from "react";
import Tab from "./Components/Tab/Tab";
import "./Tabs.scss";

class Tabs extends Component {
  render() {
    const { tabsData, currentId, handleTab } = this.props;

    return (
      <ul className="tabs">
        {tabsData.map((tab) => (
          <Tab
            key={tab.id}
            id={tab.id}
            name={tab.name}
            count={tab.count}
            handleTab={handleTab}
            currentId={currentId}
          />
        ))}
      </ul>
    );
  }
}

export default Tabs;

Tab Component

import React, { Component } from "react";
import "./Tab.scss";

class Tab extends Component {
  render() {
    const { currentId, handleTab, name, count, id } = this.props;

    return (
      <li
        id={id}
        onClick={() => handleTab(id)}
        className={`tab ` + (currentId === id && "on")}
      >
        {name}
        <span className="countTotal">{count}</span>
      </li>
    );
  }
}

export default Tab;

mockdata.json

{
  "data": {
    "tabs": [
      {
        "id": 1,
        "name": "스토리",
        "count": null
      },
      {
        "id": 2,
        "name": "반환정책",
        "count": 3
      },
      {
        "id": 3,
        "name": "커뮤니티",
        "count": 18
      }
    ]
  }
}
profile
UX에 관심 많은 개발 초보 Front-end Developer

0개의 댓글