본 포스트에서는 리액트와 타입스크립트를 이용하여 네이버 API로 데이터를 검색해서 목록 화면을 구성해 보겠습니다.
 
 

React + TypeScript Naver API로 목록 만들기

 

저번 포스트에서 뉴스와 도서 목록을 각각 구성해서 목록 UI를 완성해 보았습니다. 뉴스와 도서를 전환하기 위해서는 소스코드를 수정해가면서 조회해야 하는 부분까지 작성해 보았고, 이번 포스트에서는 [뉴스]와 [도서] 탭을 선택하면 원하는 데이터 목록이 조회되도록 탭 기능을 추가해 보겠습니다.

 

탭은 이전 React 버전과 동일하게 아래와 같은 모양으로 만들어 보겠습니다.

먼저 탭에서 사용할 데이터의 타입을 정의해 보겠습니다. /src/interface/ 아래에 tabinfo.interface. ts 파일을 생성하고 아래와 같이 작성합니다.

interface ITabInfo {
    tabId: string;
    tabName: string;
    on: boolean;
}

export {
    ITabInfo
}

 

탭 아이디와 탭 이름은 string으로 선언하고 탭의 on/off를 나타내는 "on" 항목은 boolean으로 선언합니다.

 

다음에는 /src/component/ 하위에 tablist.component.tsx 파일을 생성합니다. 먼저 탭 한 칸에 대한 컴포넌트를 작성해 보겠습니다. 아래 소스코드를 참고하여 작성합니다.

const Tab = ({ tabId, tabName, on }: ITabInfo) => {
    const changeTab = (id: string) => {
        (document.querySelector('.tabList li a.on') as HTMLAnchorElement).classList.remove('on');
        (document.querySelector('.tabList li a#' + id) as HTMLAnchorElement).classList.add('on');
    };

    return (
        <li>
            <a href="#"
                id={tabId}
                className={on ? 'on' : ''}
                onClick={() => changeTab(tabId)}>
                <span>{tabName}</span>
            </a>
        </li>
    )
};

Tab 컴포넌트는 ITabInfo 타입의 props를 전달 받습니다. 일단 changeTab 함수는 사용자가 탭을 선택했을 때 토글하는 코드만 작성합니다. TypeScript를 사용하지 않고 JavaScript만 사용할 때에는 아래와 같이 작성 했었지만 TypeScript를 사용하면 오류가 발생합니다.

컴파일 타이밍에는 해당 Selector의 결과가 있는지 없는지 알 수가 없어, 위와 같은 오류를 발생 시킵니다. 하여 TypeScript를 사용할 경우 아래 세 가지 방법 중 하나를 선택하여 소스코드를 작성합니다.

 

1. 해당 Selector의 결과에 대한 타입을 명확하게 선언합니다.

(document.querySelector('.tabList li a.on') as HTMLAnchorElement).classList.remove('on');
(document.querySelector('.tabList li a#' + id) as HTMLAnchorElement).classList.add('on');

 

2. ? (Optional) 연산자를 사용해 해당 Selector의 결과가 필수가 아님으로 선언합니다. 이 방법은 컴파일 오류는 피할 수 있지만 의미가 맞지는 않아 보입니다.

document.querySelector('.tabList li a.on')?.classList.remove('on');
document.querySelector('.tabList li a#' + id)?.classList.add('on');

 

3. ! (null forgiving) 연산자를 사용해 해당 Selector는 null이나 undefined가 할당되지 않을 것이라는 것을 표현합니다.

document.querySelector('.tabList li a.on')!.classList.remove('on');
document.querySelector('.tabList li a#' + id)!.classList.add('on');

 

Runtime에 영향을 주는 코드는 아니지만 각 방법 중 가장 의미에 맞는 방법을 사용하시면 될 것 같습니다.

 

 

다음엔 Tab 컴포넌트를 사용해 전체 탭 (뉴스, 도서)을 표시하는 TabList 컴포넌트를 아래와 같이 작성합니다.

const TabList = () => {
    const tabList: ITabInfo[] = [
        {tabName: '뉴스', tabId: 'news', on: true},
        {tabName: '도서', tabId: 'book', on: false}
    ];

    return (
        <div className="tabBox">
            <ul className="tabList">
            {
                tabList.map((v: ITabInfo, inx: number) => {
                    return <Tab key={inx} {...v} />
                })
            }
            </ul>
        </div>
    )
};

export default TabList;

ITabInfo 형식의 배열로 탭 메뉴를 선언한 후 해당 탭의 개수만큼 <Tab /> 컴포넌트를 Rendering 합니다. 이 때 뉴스는 초기에 활성화해야 하므로 "on" 항목은 true로 선언합니다. Tab 컴포넌트에 props를 전달할 때는 스프레드 구문을 사용하여 ITabInfo 형태의 Object를 모두 전달합니다. 

 

tablist.component.tsx 프로그램의 현재까지의 소스코드는 아래와 같습니다.

import { ITabInfo } from "../interface/tabinto.interface";

const Tab = ({ tabId, tabName, on }: ITabInfo) => {
    const changeTab = (id: string) => {
        (document.querySelector('.tabList li a.on') as HTMLAnchorElement).classList.remove('on');
        (document.querySelector('.tabList li a#' + id) as HTMLAnchorElement).classList.add('on');
    };

    return (
        <li>
            <a href="#"
                id={tabId}
                className={on ? 'on' : ''}
                onClick={() => changeTab(tabId)}>
                <span>{tabName}</span>
            </a>
        </li>
    )
};

const TabList = () => {
    const tabList: ITabInfo[] = [
        {tabName: '뉴스', tabId: 'news', on: true},
        {tabName: '도서', tabId: 'book', on: false}
    ];

    return (
        <div className="tabBox">
            <ul className="tabList">
            {
                tabList.map((v: ITabInfo, inx: number) => {
                    return <Tab key={inx} {...v} />
                })
            }
            </ul>
        </div>
    )
};

export default TabList;

 

App 컴포넌트에 TabList 컴포넌트를 표시하기 위해 app.tsx 프로그램에 <TabList /> 컴포넌트를 추가합니다.

import ListView from "./component/listview.component";
import TabList from "./component/tablist.component";

const App = () => (
    <div>
        <ListView />
        <TabList />
    </div>
)

export default App;

 

마지막으로 TabList UI를 위해 아래 css를 main.css 파일에 추가합니다.

.tabBox{position:fixed;bottom:0px;width:100%;height:50px;border-top:1px solid #DDD;background-color:#FFF;}
.tabList{display:table;width:100%;table-layout:fixed;}
.tabList li{display:inline-block;text-align:center;width:49%;}
.tabList li:first-child{border-right:1px solid #DDD;}
.tabList li a{display:block;color:#000;font-size:1.6rem;line-height:48px;}
.tabList li a span{display:inline-block;padding:0px 8px;width:100%;}
.tabList li a.on span{background-color:#999;color:#FFF;font-weight:700;}

 

이제 UI 확인을 위해 컴파일, 번들링 한 후 확인합니다.

D:\workspace\searchNaverApiTs> tsc
D:\workspace\searchNaverApiTs> npm run build

 

확인을 해보면 탭 UI가 잘 표현되고, 토글도 정상적으로 동작합니다.

[도서] 탭 선택 시 토글 확인
[뉴스] 탭 선택 시 토글 확인

 

하지만 아직 선택한 탭에 맞는 API를 호출하여 데이터를 바인드 하는 부분은 작성해 보지 않았습니다. 이번 글이 좀 길어진 관계로 해당 내용들은 다음 포스트에서 진행 해보겠습니다.

300x250

+ Recent posts