--
이제 화면에 Header 영역까지 만들어 보았습니다. 다음은 뉴스, 도서, 영화 중 검색할 API를 선택할 수 있는 Tab Menu 부분을 만들어 보겠습니다.
Tab Menu의 요건은 아래와 같습니다.
1. 최초에는 뉴스가 "선택됨" 상태로 표시됩니다.
2. Menu를 클릭하면 클릭한 메뉴는 "선택됨" 상태가 되고, 나머지 메뉴는 "선택됨" 상태가 해제됩니다.
현재 우리가 개발한 main.js는 아래와 같은 모양입니다.
ReactDOM.render(<Header />, document.getElementById('app'));
하지만 하나의 화면에는 Header도 들어가야 하고, TabList, ListView도 들어가야 하니 페이지 역할을 할 Component 하나를 생성해 보겠습니다.
/src 하위에 pages라는 디렉토리를 생성하고 Main.jsx 파일을 생성합니다.
/src/pages/Main.jsx 파일은 아래와 같이 코딩합니다. 별 내용은 없고, 우리가 만들 Header와 TabList의 Rendering 영역을 잡아주는 역할을 합니다.
import React from "react";
import Header from "../components/layout/Header.jsx";
import TabList from "../components/layout/TabList.jsx";
const Main = (props) => {
return (
<>
<Header />
<div className="content">
<TabList />
</div>
</>
);
};
export default Main;
Header 영역 하위에 content class를 사용하는 div를 생성하고 TabList Component를 넣습니다. JSX Element들은 반드시 최상위 태그 하위에 포함되어야 하므로 최상위에는 <></> 태그 하위에 포함시켰습니다.
이제 위에서 사용한 TabList Component를 만들어보겠습니다.
먼저 3개의 Tab으로 구성하기 위해 탭 목록을 작성합니다.
const tabList = [
{tabName: '뉴스', id: 'news', isOn: true},
{tabName: '도서', id: 'book', isOn: false},
{tabName: '영화', id: 'movie', isOn: false}
];
각 Tab의 너비는 화면의 1/3 씩 차지해야 하므로 너비를 계산합니다.
const minWidth = Math.floor(window.innerWidth / tabList.length);
TabList는 목록이므로 <ul> 태그를 작성하고 위에서 선언한 tabList 배열의 개수만큼 반복하며 Tab을 표시하는 코드를 작성합니다.
return (
<div className="tabBox">
<ul className="tabList" role="tablist">
{
tabList &&
tabList.map(v => {
return <Tab key={v.id}
tab={v}
minWidth={minWidth} />
})
}
</ul>
</div>
);
위와 같이 Element를 반복할 때 각 Element를 구분할 수 있는 key props으로 전달하지 않으면 React는 아래와 같은 오류를 반환합니다.
react.development.js:217 Warning: Each child in a list should have a unique "key" prop.
Check the render method of TabList
. See https://reactjs.org/link/warning-keys for more information.
at Tab (webpack://testPrj/./src/components/layout/TabList.jsx?:13:23)
해당 값은 React가 Rendering 할 때 필요한 고유한 값을 전달해야 합니다. map에서 (v, inx)와 같이 inx를 추출하여 넘겨도 동작은 하지만 이렇게 변경될 수도 있는 값을 넘기면 side effect가 발생할 경우 한참을 헤메어야 하는 경우도 있으니 되도록이면 배열의 index 값은 사용을 지양합니다.
현재까지 TabList.jsx의 내용은 아래 코드와 같습니다.
const TabList = () => {
const tabList = [
{tabName: '뉴스', id: 'news', isOn: true},
{tabName: '도서', id: 'book', isOn: false},
{tabName: '영화', id: 'movie', isOn: false}
];
const minWidth = Math.floor(window.innerWidth / tabList.length);
return (
<div className="tabBox">
<ul className="tabList" role="tablist">
{
tabList &&
tabList.map(v => {
return <Tab key={v.id}
tab={v}
minWidth={minWidth} />
})
}
</ul>
</div>
);
};
export default TabList;
위의 코드를 보면 TabList Component는 Tab Component를 반복적으로 Rendering 하고 있습니다. Tab Component를 작성해보겠습니다.
일단 TabList에서 Tab을 생성할 때 props는 크게 2가지를 전달했습니다. tab이라는 props에는 Object 전체를 전달했고, minWidth에는 화면 너비의 1/3 값을 계산하여 전달했습니다. 해당 내용을 고려하면서 아래 코드를 보겠습니다.
먼저 props에서 사용할 변수를 추출합니다.
const tabName = props.tab.tabName;
const isOn = props.tab.isOn;
const tabId = props.tab.id;
다음은 Tab을 클릭했을 때 실행시킬 함수를 작성합니다. 지금 구현하는 기능은 이전에 on class가 표시되어 있던 Tab에서는 on class를 제거하고, 현재 클릭한 Tab에 on class를 추가해주는 부분까지만 구현하겠습니다.
const changeTab = () => {
document.querySelector('.tabList li a.on').classList.remove('on');
document.querySelector('.tabList li a#' + tabId).classList.add('on');
};
현재 Tab은 tabList class를 사용하는 <ul> 태그 하위의 <li> 태그로 구성합니다. 위의 코드는 현재 목록 중 on class를 가진 목록에서 on class를 제거하고, 현재 tabId를 사용하는 Tab에 on class를 추가하는 코드입니다.
다음은 Element를 return 하는 부분입니다.
return (
<li role="presentation" style={{minWidth: props.minWidth + 'px'}}>
<a href="#"
role="tab"
tabIndex="0"
id={tabId}
aria-selected={isOn.toString()}
className={isOn ? 'on' : ''}
onClick={changeTab}>
<span>{tabName}</span>
</a>
</li>
);
<ul> 태그 하위에 반복할 내용이므로 <li> 태그를 사용합니다. 실제 클릭하는 부분은 anchor 태그로 구성했으며 위와 같이 속성을 설정합니다.
aria-selected 속성은 접근성을 위한 코드이며, 'true'나 'false'가 문자열로 설정되어야 하므로 toString() method로 문자열로 변환하여 설정합니다. className은 사용자의 선택에 따라 on과 공백을 설정할 수 있도록 분기합니다.
현재까지 작성한 TabList.jsx 전체 코드는 아래와 같습니다.
import React from 'react';
const Tab = (props) => {
const tabName = props.tab.tabName;
const isOn = props.tab.isOn;
const tabId = props.tab.id;
const changeTab = () => {
document.querySelector('.tabList li a.on').classList.remove('on');
document.querySelector('.tabList li a#' + tabId).classList.add('on');
};
return (
<li role="presentation" style={{minWidth: props.minWidth + 'px'}}>
<a href="#"
role="tab"
tabIndex="0"
id={tabId}
aria-selected={isOn.toString()}
className={isOn ? 'on' : ''}
onClick={changeTab}>
<span>{tabName}</span>
</a>
</li>
);
};
const TabList = () => {
const tabList = [
{tabName: '뉴스', id: 'news', isOn: true},
{tabName: '도서', id: 'book', isOn: false},
{tabName: '영화', id: 'movie', isOn: false}
];
const minWidth = Math.floor(window.innerWidth / tabList.length);
return (
<div className="tabBox">
<ul className="tabList" role="tablist">
{
tabList &&
tabList.map(v => {
return <Tab key={v.id}
tab={v}
minWidth={minWidth} />
})
}
</ul>
</div>
);
};
export default TabList;
Header의 역할은 하나의 화면에서 Header 영역만을 담당하는 Component 였습니다. 이제 Header나 TabList를 표시할 화면을 하나 생성하겠습니다.
/src/pages/Main.jsx 파일을 생성하고 아래와 같이 코딩합니다.
import React from "react";
import Header from "../components/layout/Header.jsx";
import TabList from "../components/layout/TabList.jsx";
const Main = (props) => {
return (
<>
<Header />
<div className="content">
<TabList />
</div>
</>
);
};
export default Main;
Main이라는 Component를 생성하고 화면에 Header와 TabList Component를 포함하도록 하였습니다.
현재 main.js에는 Header Component만 Rendering 하게 되어 있으므로 main.js를 열어 Main Component를 Rendering 하도록 변경합니다.
아래 코드와 같이 변경합니다.
import React from 'react';
import ReactDOM from 'react-dom';
import Main from './pages/Main.jsx';
ReactDOM.render(<Main />, document.getElementById('app'));
여기까지 작업을 하고 나면 코딩은 일단 완료되었습니다. TabList의 모양을 잡아줄 CSS를 몇 줄 추가하겠습니다.
main.css 파일을 열어 아래 내용을 추가합니다.
.content{position:absolute;top:52px;bottom:0px;overflow-x:hidden;}
.tabBox{position:relative;width:100%;height:50px;border-bottom:1px solid #DDD;}
.tabList{display:table;width:100%;table-layout:fixed;}
.tabList li{display:inline-block;text-align:center;}
.tabList li a{display:block;color:#000;font-size:1.6rem;line-height:48px;}
.tabList li a span{display:inline-block;padding:0px 8px;}
.tabList li a.on span{border-bottom:2px solid #000;font-weight:700;}
이제 다시 http://localhost:8080 을 실행해보면 아래 그림과 같이 Header 및 Tab Menu가 표시되고, Tab을 클릭할 때마다 "선택됨" 상태가 변경되는 화면이 완성되었습니다.