입문 #10 - Tab 이동 구현 (1/2)

8379 단어 ReactReact

뉴스 목록의 Layout과 도서 목록의 Layout까지 구현해 보았습니다. 이제 실제로 Tab Menu를 click하면 해당 tab으로 이동해서 각자 tab에 맞는 API를 호출하여 데이터를 가져오는 부분까지 구현해 보겠습니다.

먼저 생각해보면 어느 tab을 선택했는지는 TabList의 status일 것 같고, 그 선택을 ListView에 넘겨줘서 데이터를 조회해야 하니 그 부분은 props로 데이터를 전달해야 할 것 같습니다.

대강 코드를 상상해보면 아래와 같습니다.

//TabList
const [selectedTab, setSelectedTab] = useState('news');

const changeTab = () => {
	//Tab ui on/off 처리
    setSelectedTab(현재 tab);
};

//ListView Rendering
<ListView selectedTab={selectedTab} />

위와 같은 구성이 될 것 같은데 Component들이 배열되어 있는 형태를 생각해보면 어떤 식으로 props를 전달해야 할 지도 상당히 어렵습니다.

Redux 사용

하여 우리는 전체 애플리케이션의 상태를 component에 종속되지 않고 관리하기 위해서 Redux를 사용합니다. 프로젝트의 가장 상위에서 store를 생성하여 전체 애플리케이션의 상태를 저장하고 각 component는 해당 store를 describe하여 상태의 변경을 감지하게 됩니다.

Redux는 facebook에서 만든 flux 패턴을 따릅니다.

Action -> Dispatcher -> Model -> View

검색을 하다보니 아래 Link가 잘 정리되어 있는 것 같습니다. 쉽게 잘 설명되어 있어 한 번씩 읽어보시면 좋을 것 같습니다.
https://www.huskyhoochu.com/flux-architecture/

우리는 Redux를 사용해서 component에서 필요한 시점에 action을 발동시켜 dispatch 함수를 통해 store의 상태를 변경시키고, view component에서는 해당 상태를 describe 하여 view에 반영해보도록 하겠습니다.

Redux 설치

프로젝트 root에서 아래 명령어로 Redux를 설치해 보겠습니다.

yarn add [email protected]
yarn add [email protected]
yarn add [email protected]
yarn add [email protected]

reducer.js

Redux 관련 로직을 작성하기 위해 /src/config/reducer.js 파일을 하나 생성합니다.

먼저 아래와 같이 action을 정의합니다. 현재 선택된 tab을 관리할 예정이므로 CUR_TAB으로 명명하겠습니다.

export const CUR_TAB = 'CUR_TAB';
export const setCurTab = tabId => ({type: CUR_TAB, tabId});

선택된 tab에 대한 상태를 입력 받아 새로운 상태를 return 하는 reducer를 하나 생성하겠습니다.

const rootReducer = (state = {tabId: 'news'}, action) => {
    switch (action.type) {
        case CUR_TAB:
            return {
                ...state,
                tabId: action.tabId
            };
        default:
            return state;
    }
};

tabId의 초기값은 첫번째 tab인 news 탭으로 정의합니다.

그 다음 model에 해당하는 store를 아래 코드와 같이 생성합니다. 일단 redux 상태 변경에 따라 logging 할 수 있게 composeWithDevTools를 사용하여 아래와 같이 작성합니다.

const enhancer = composeWithDevTools(applyMiddleware(logger));
export const store = createStore(rootReducer, enhancer);

Root.js

이제 위에서 만든 store를 전역으로 지정하는 부분을 설정하겠습니다.
현재 우리는 메인 화면을 main.js에서 <Main /> component를 바로 불러와서 사용하고 있습니다.

store도 설정하고 나중에 페이지도 여러 개가 생길 수 있기 때문에 해당 설정들을 관리하는 파일을 하나 더 만들어 보겠습니다.

/src/Root.js를 생성하고 아래 코드와 같이 작성합니다.

import React from 'react';
import { Provider } from 'react-redux';
import { store } from './config/reducer';
import Main from './pages/Main.jsx';

const Root = () => {
    return (
        <Provider store={store}>
            <Main />
        </Provider>
    );
};

export default Root;

main.js는 아래와 같이 변경합니다.

import React from 'react';
import ReactDOM from 'react-dom';
import Root from './Root';

ReactDOM.render(<Root />, document.getElementById('app'));

TabList.jsx

이제 tab을 변경할 때마다 redux의 state를 변경하기 위해 action을 dispatch 하는 부분을 추가해야 합니다.

개발해 놓은 Tab component에 useDispatch를 추가합니다.

const Tab = (props) => {
    const tabName = props.tab.tabName;
    const isOn = props.tab.isOn;
    const tabId = props.tab.id;
    // 추가
    const dispatch = useDispatch();
    ...

tab 변경 시 실행할 changeTab 함수에는 tab 변경 action을 dispatch 하는 코드를 아래와 같이 추가합니다.

const changeTab = () => {
    document.querySelector('.tabList li a.on').classList.remove('on');
    document.querySelector('.tabList li a#' + tabId).classList.add('on');
    //추가
    dispatch(setCurTab(tabId));
};

ListView.jsx

ListView에서 실제 NAVER API를 호출하는 부분을 아래와 같이 수정해야 합니다.

1. 변경된 tabId 기준의 API를 호출해야 합니다.
2. tabId가 변경되었을 때만 API를 호출해야 합니다.

먼저 ListView Component 상단에서 useSelector를 사용해서 tab click 시 저장한 tabId를 받아 옵니다.

const [articles, setArticles] = useState(null);
//추가
const tabId = useSelector(state => state.tabId);

그 다음 받아온 tabId를 useEffect에서 NAVER API 호출 시 사용하도록 하고, useEffect의 마지막 인자에 빈 배열 대신 tabId를 넘겨 tabId가 변경될 경우만 useEffect를 발생시키도록 합니다.

useEffect(() => {
    let apiUrl = 'https://openapi.naver.com/v1/search/' + tabId + '?query=올림픽';
    axios.get(apiUrl, {
        headers: {
            'Content-Type': 'application/json',
            'X-Naver-Client-Id': NAVER_CLIENT_ID,
            'X-Naver-Client-Secret': NAVER_CLIENT_SECRET
        }
    })
    .then(({data}) => {
        setArticles(data.items);
    })
    .catch(e => {
        console.error(e.stack);
    });
}, [tabId]);

그 다음 UI Rendering 부분도 tabId 값에 따라 변경하도록 작성합니다.
아직 영화 View는 작성하지 않았으므로 뉴스와 도서만 작성합니다.

return (
    <ul className='listView'>
    {
        articles &&
        (rdCurTab.tabId === 'news') ?
            articles.map((v, inx) => {
               return <NewsRow key={inx} row={v} />
            })
        : (rdCurTab.tabId === 'book') ? 
            articles.map((v, inx) => {
                return <BookRow key={inx} row={v} />
            })
        :  ''
    }
    </ul>
);

뉴스와 도서 탭을 이동하면 정성스레 만든 layout 대로 목록이 잘 표시됨을 확인할 수 있습니다.

[뉴스 tab]

[도서 tab]

그리고 console을 확인하시면 redux의 상태가 변경될 때마다 개발 로그가 표시됨을 알 수 있습니다. redux에 저장한 상태값을 확인할 필요가 있을 경우 해당 로그를 참고하시면 됩니다.

다음 게시글에서는 이번 게시글에서 했던 내용에 대해 다시 정리하겠습니다.

좋은 웹페이지 즐겨찾기