React/게시판만들기 v2.

[React][CRUD] 게시판 만들기 All in One (4). 게시판 따라 다른 게시글 목록 불러오기, redux, redux-saga, redux-toolkit, react, axios

binaryJournalist 2021. 4. 14. 14:53
반응형

 

 

목차돌아가기:

binaryjourney.tistory.com/pages/ReactCRUD-create-board-tutorial-v2

 

[React][CRUD] create-board-tutorial-v2

* 인용시 출처 부탁드립니다. 완성 소스 code: github.com/cruellaDev/react-create-board-v2 cruellaDev/react-create-board-v2 updated version of react-create-board. Contribute to cruellaDev/react-create-..

binaryjourney.tistory.com

 

 

 

 

조회 방식은 지난번 게시판 board 불러오는 것과 거의 비슷하다. 그러나 이번에는 articles 내 데이터 중 원하는 boardId 를 가진 것들만 불러올 것이다.

 

 

게시글 목록 불러오는 것과 한 게시글의 내용을 불러오는 것은 같은 slice와 saga를 쓸 것이다. (articleSlice, articleSaga)

 

 

Routes에서 정해놓은 바에 따라 게시판 중 "일기" 와 "할일" 을 클릭하면 ArticleList 컴포넌트가 렌더링될 것이다.

 

// Routes.js
const ArticleList = lazy(() => import('../views/ArticleList'));
<Route path={"/board/:boardId"} exact component={ArticleList} />

// Board.js
<Link to={{ pathname: `/board/${board?.id}` }}>

 

 

 

나는 url에서 게시판 id  parameter 만 받아와서 조회하면 된다.

 

쓸 훅은 useParams, useSelector, useEffect가 다일 듯하다.

 

껍데기만 만들어놓은 articleSlice에 initialState 와 reducers 내용을 만들어주자.

 

 

// artilceSlice.js

import { createSlice } from "@reduxjs/toolkit";

const name = "article";

const initialState = {
    articleList: [],
    status: 0,
    statusText: "Loading",
};

const reducers = {
    getArticleList: (state, action) => {},
    getArticleListSuccess: (state, action) => {
        state.articleList = action.payload?.data ?? [];
        state.status = action.payload?.status;
        state.statusText = action.payload?.statusText ?? "Success";
    },
    getArticleListFail: (state, action) => {
        state.articleList = initialState.articleList;
        state.status = action.payload?.status ?? 500;
        state.statusText = action.payload?.statusText ?? "Network Error";
    }
}

const articleSlice = createSlice({
    name,
    initialState,
    reducers
});

export const articleReducer = articleSlice.reducer;
export const articleActions = articleSlice.actions;

 

이름만 바뀌었을 뿐이지 boardSlice.js 와 거의 똑같다.

 

 

articleSaga.js 도 마찬가지다.

 

너무 똑같아서 변화를 주기로 하였다. 큰 변화는 아니고 다른 메서드 사용을 시도해보겠다.

 

// articleSaga.js

import { all, call, retry, fork, put, take, select } from 'redux-saga/effects';
import { articleActions } from '../slices/articleSlice';
import axios from '../utils/axios';
import qs from "query-string";

const SECOND = 1000;

// api 서버 연결 주소
function apiGetArticle(articleId) {
    return axios.get(`articles/${articleId}`);
}

function apiGetArticleList(requestParams) {
    return axios.get(`articles?${qs.stringify(requestParams)}`);
}

// api 서버 연결 후 action 호출
function* asyncGetArticleList(action) {
    try {
        // const response = yield call(apiGetArticleList, { boardId: action.payload });
        const response = yield retry(3, 10 * SECOND, apiGetArticleList, { boardId: action.payload });
        if (response?.status === 200) {
            yield put(articleActions.getArticleListSuccess(response));
        } else {
            yield put(articleActions.getArticleListFail(response));
        }
    } catch(e) {
        yield put(articleActions.getArticleListFail(e.response));
    }
}

// action 호출을 감시하는 watch 함수
function* watchGetArticleList() {
    while(true) {
        const action = yield take(articleActions.getArticleList);
        yield call(asyncGetArticleList, action);
    }
}

export default function* articleSaga()
{
    yield all([fork(watchGetArticleList)]);
}

 

boardSaga 와 다른 점은 우선 query-string 이 사용됐다 (이건 위에서 말한 변화가 아니다.)

 

 

function apiGetArticleList(requestParams) {
    return axios.get(`articles?${qs.stringify(requestParams)}`);
}

 

requestParam 은 { baordId: 숫자 } 이고

query-string은 이걸 url 의 query-string으로 바꾸어준다.

 

그래서 호출되는 url은

articles?boardId=숫자 가 된다.

 

 

 

변화를 준 부분은 이 부분이다.

const response = yield retry(3, 10 * SECOND, apiGetArticleList, { boardId: action.payload });

 

retry 메서드는 call 을 가지고 있다. 다만 이름 그대로 retry로 주어진 조건에 따라 call 을 계속 시도하는 것이다.

 

redux-saga.js.org/docs/api/#retrymaxtries-delay-fn-args

 

API Reference | Redux-Saga

API Reference

redux-saga.js.org

 

위 API Reference를 참고하여 따라해보았다.

 

yield retry(횟수, 시간(mills), 호출함수, 호출함수input);

 

형식은 이러하다.

 

첫번째 call이 실패했을 때 정해진 시간 간격마다 정해진 횟수 안에 연결이 성공할 때까지 call을 시도한다.

 

 

// rootSaga.js

import { map } from 'ramda';
import { all, fork  } from "redux-saga/effects"
import articleSaga from "./sagas/articleSaga";
import boardSaga from "./sagas/boardSaga";

let combineSagas = {};
combineSagas = Object.assign(combineSagas, { articleSaga, boardSaga });

export default function* rootSaga() {
    yield all(map(fork, combineSagas));
}

 

rootSaga 에 articleSaga를 넣어주고

 

 

 

이제 ArticlesList.js 에서는 훅만 제대로 import만 해주면 된다! 형태도 Board와 거의 비슷하다.

 

// ArticleList.js

import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link, useParams } from 'react-router-dom';
import { articleActions } from '../slices/articleSlice';

function ArticleList() {
    const params = useParams();
    const { articleList, status, statusText } = useSelector((state) => state.articleReducer);
    const boardList = useSelector((state) => state.boardReducer.boardList);
    const dispatch = useDispatch();
    useEffect(() => {
        dispatch(articleActions.getArticleList(params?.boardId ?? 0));
    }, [dispatch, params?.boardId]);
    return (
        <>
            {
                status === 200 ?
                    <>
                        <div>
                            <span>게시판: </span>
                            <span>
                                {
                                    boardList.length > 0 &&
                                    boardList.find((board) => board.id === parseInt(params?.boardId))?.name
                                }
                            </span>
                        </div>
                        { articleList.length > 0 ?
                            <div>
                                <div>
                                    {
                                        articleList.map((article, index) => 
                                            <div  key={article?.id ?? index}>
                                                <Link to={{ pathname: `/article/${article?.id ?? 0}` }}>
                                                    <span>{article?.title ?? ""}</span>
                                                </Link>
                                            </div>
                                        )

                                    }
                                </div>
                            </div>
                        :
                            <div> 게시글이 없습니다. </div>
                        }
                    </>
                :
                    <div>
                        <div>
                            <span>{status}</span>
                        </div>
                        <div>
                            <span>{statusText}</span>
                        </div>
                    </div>
            }
        </>
    );
}

export default ArticleList;

 

useParams 는 url 중 parameter로 보낸 것만 받아주는 훅이다.

 

url에서 /숫자?  숫자를 보통 parameter(param)로 칭하고 ?key=value&key=value 이걸 query로 칭하는데 useParam 은 딱 param까지만 가져와준다.

 

 

다른 정보까지 얻고 싶다면 react-router 들의  훅 useRouterMatch, useLocation 이 있는데

 

const match = useRouterMatch();

const location = useLocation();

을 작성한 뒤 console에 값이 어떻게 나오는지 확인하는 것도 추천한다.

 

react-router 는 알면 알수록 재밌다.

 

 

 

화면이 제대로 나온다면 이렇게 나올 것이다.

 

 

 

 

 

 

 

 

목차돌아가기:

binaryjourney.tistory.com/pages/ReactCRUD-create-board-tutorial-v2

 

[React][CRUD] create-board-tutorial-v2

* 인용시 출처 부탁드립니다. 완성 소스 code: github.com/cruellaDev/react-create-board-v2 cruellaDev/react-create-board-v2 updated version of react-create-board. Contribute to cruellaDev/react-create-..

binaryjourney.tistory.com

 

 

 

반응형