웹사이트를 이용하다보면 탭을 이용해 한 페이지에서 다양한 정보를 보여주는 경우가 많다.
이런 구조는 대체로 메뉴 탭을 누를 때마다 해당 탭 id와 동일한 id를 갖고 있는 컴포넌트를 렌더해주는 형태로 되어 있다. 함께 구현해보자!
Product 페이지 컴포넌트 내부에 Tabs(map method로 Tab 컴포넌트 반복 렌더)
> Tab
가 위치할 수 있도록 한다.
컴포넌트 import, export는 필수!
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;
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;
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;
{
"data": {
"tabs": [
{
"id": 1,
"name": "스토리",
"count": null
},
{
"id": 2,
"name": "반환정책",
"count": 3
},
{
"id": 3,
"name": "커뮤니티",
"count": 18
}
]
}
}
들어오는 데이터에 따라 갯수가 달라지는 탭 컴포넌트를 map 메소드로 반복시켜주려면 우선 목데이터가 필요하다. 이번에 만들 탭은 탭 이름과 함께 탭 내부 컨텐츠 갯수를 보여주고 있기 때문에 name과 count를 데이터로 넣어주었다.
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' 생략)로 받아온다.
{tabsData && (
<>
<Tabs
tabsData={tabsData}
/>
<div className="contentsContainer">
<content>렌더될 컴포넌트 위치</content>
</div>
</>
)}
컴포넌트의 lifecycle을 위해 tabsData가 있는 경우에만 렌더되도록 조건부 렌더링을 설정해준다.
또한 fetch로 받은 데이터를 Tabs와 Tab 컴포넌트에 props로 전달한다.
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로 데이터를 전달한다.
const { id, name, count } = this.props;
return (
<li
id={id}
className="tab"
>
{name}
<span className="count">{count}</span>
</li>
);
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를 보내주었다.
constructor() {
super();
this.state = {
currentId: 1,
};
}
handleTab = (id) => {
this.setState({
currentId: id,
});
};
기본으로 보여주어야 할 탭 메뉴의 id를 state에 설정하고, 클릭 이벤트가 생길 시 실행될 함수에 setState로 id를 넣어 클릭 시 컴포넌트가 새로 렌더되도록 한다.
<Tabs handleTab={handleTab} />
<Tab handleTab={handleTab} />
함수도 데이터와 마찬가지로 props로 전달해준다.
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에 해당하는 컴포넌트를 선언한다.
<div className="contentsContainer">
<content>{MAPPING_TAB[currentId]}</content>
</div>
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;
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;
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;
{
"data": {
"tabs": [
{
"id": 1,
"name": "스토리",
"count": null
},
{
"id": 2,
"name": "반환정책",
"count": 3
},
{
"id": 3,
"name": "커뮤니티",
"count": 18
}
]
}
}