ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [react] function component에서 redux-saga로 api 호출 및 화면에 리스트 불러오기
    개발자의 공부는 은퇴까지 필수다/node js & react 2020. 11. 30. 20:25

    현재 나는 react "학습중"이다. 메모하려고 블로그를 쓰고있지만 부족한 부분이 많을 수도 있다.

    생각보다 인강을 듣고 검색하고 해도 배운것과 다른 또다른 방식에 접하면 변경하기가 쉽지는 않은 것 같다.

     

    inflean 강의에서는 function component 로 정의하고, redux-thunk로 action을 실행시키는 dispatch에 함수를 전달할 수 있도록 하여 callback 방식을 자주 사용하던 나에게는 구현이 매우 간단한데

    현재 사용하는 템플릿은 class component로 컴포넌트를 정의하고, redux-saga를 사용하여 firebase에서 데이터를 가져오곤 한다.

    redux-saga의 러닝커브가 꽤나 높다고 하는데 내가 어려웠던 이유는 생각보다 인터넷에 예제가 별로 없었다. ㅠㅠ

     

    우선 며칠동안 별거 아닌걸로 헤매이다가 겨우 데이터를 제대로 가져와 화면에 나타내는데에 성공했는데, 구조방면에서나 뭐가 잘못된지는 잘 모르겠지만 우선 완성은 했으니까 까먹기 전에 써놓으려고 한다. ㅋㅋㅋ

     

    우선 구현하는데에 가장 중요한 폴더로는 액션을 정의하는 actions, reducer를 정의하는 reducers, sagas를 모아두는 sagas 폴더를 만든다.

     

    내가 구현하는것은 지점관련된 데이터이기 때문에 대부분 폴더명이나 변수명이 branch로 되어있다.

    1. 먼저 /src/actions/Branch.js

    import {GET_BRANCH_LIST, SHOW_MESSAGE, GET_BRANCH_SUCCESS} from "../constants/ActionTypes";
    
    export const getBranchList = ( param ) => {
        return {
            type: GET_BRANCH_LIST,
            payload: param
        }
    };
    
    
    export const fetchGetBranchListSuccess = ( data ) => {
    
        return {
            type: GET_BRANCH_SUCCESS,
            payload: data
        }
    };
    
    //  에러 발생 시 message를 보여줌
    export const showBranchMessage = (message) => {
        return {
            type: SHOW_MESSAGE,
            payload: message
        };
    };
    
    

    action에는 실행할 함수 세가지를 정의한다. ( api 호출 액션, api 호출 시 액션, api 호출 실패시 액션 )

     

    /src/actions/index.js

    export * from './Branch';

     

    2. 보통 실행하는 action type이름은 따로 정의한다.

    /src/constants/ActionType.js

    export const GET_BRANCH_LIST= 'get_branch_list';
    export const GET_BRANCH_SUCCESS= 'get_branch_list_success';

     

    3. 그리고 state를 전달받을 reducer

    /src/reducers/Branch.js

    import {
        GET_BRANCH_LIST, GET_BRANCH_SUCCESS, SHOW_MESSAGE
    } from "../constants/ActionTypes";
    
    export default (state = {}, action ) => {
    
        switch (action.type) {
            case GET_BRANCH_LIST: {
                return { ...state }
            }
            case GET_BRANCH_SUCCESS: {
                return { ...state, data: action.payload }
            }
            case SHOW_MESSAGE : {
                return { ...state, error: action.error }
            }
            default:
                return state;
        }
    }
    

    /src/reducers/index.js

    //  기능별로 reducer가 여러가지가 있을 수 있다. 여러가지로 나뉘어져있는 기능을 ROOT REDUCER에서 기능들을 하나로 합쳐준다.
    import {combineReducers} from 'redux';
    import {connectRouter} from 'connected-react-router'
    
    
    import Branch from './Branch';
    
    export default (history) => combineReducers({
      router: connectRouter(history),
      branch: Branch,
    });
    

    4. 또, 가장 중요한 saga 정의

    /src/sagas/Branch.js

    import {all, call, put, takeEvery} from 'redux-saga/effects';
    import { fetchGetBranchListSuccess,showBranchMessage } from 'actions/Branch';
    import {GET_BRANCH_LIST} from 'constants/ActionTypes';
    
    
    const getBranchListRequest = async ( param ) => {
        try{
            let url = "/admin/react/brandList?perPageNum=5";
            if( param && param.pageStart ) {
                url += "&pageStart=" + param.pageStart;
            }
            const response = await fetch( url);
            const data = await  response.json();
            return data;
        }
        catch (e) {
            console.log( e );
        }
    }
    
    /*
    const getBranchListRequest = () => {
        console.log( "get api  실행 ")
        return Axios.get(
            `/admin/react/brandList`
        );
    }
     */
    
    //  getBranchListRequest 액션이 실행되면 실행되는 saga
    function* fetchGetBranchListRequest( action ) {
    
        let param = JSON.stringify( action.payload );
        console.log( param )
    
        try {
            const data = yield call(getBranchListRequest, action.payload  );
            yield put(fetchGetBranchListSuccess(data));
        } catch (error) {
            yield put(showBranchMessage(error));
        }
    }
    
    //  rootSaga는 모든 saga를 합친다.
    export default function* rootSaga() {
        yield all([takeEvery(GET_BRANCH_LIST, fetchGetBranchListRequest)]);
    }

    /src/sagas/index.js

    import {all} from 'redux-saga/effects';
    import branchSagas from './Branch';
    
    export default function* rootSaga(getState) {
      yield all([
        branchSagas()
      ]);
    }
    

     

     

    그리고 대망의... App이나 Component쪽에서 호출하는 부분 !!

    5. BranchList.js(컴포넌트)

    검색하다보면 위에 소스들은 잘 나오는데 컴포넌트(또는 App.js와 같이 화면에 나타나는 부분)에서 어떻게 action을 호출하며, 어떻게 state를 화면에 나타내는지는 소스가 많지 않다 . 내 검색이 문제인가?

    암튼 나는 BranchList를 가져오는 Component를 정의해놓았다. 위에서 말했듯이 class component아니고, function component이다. 따라서 Hook을 사용하여 state를 가져온다.

    Pagination은 많은 css를 제공하는 ant Design에서 가져왔다.

    참고로 api를 호출했을 때 response 형태는 다음과 같다.

    {

        branchList : [ { ... }, {...} ],

        totCount : 10

    }

    import React, {useCallback, useEffect, useState} from 'react';
    import Table from '@material-ui/core/Table';
    import TableBody from '@material-ui/core/TableBody';
    import TableCell from '@material-ui/core/TableCell';
    import TableHead from '@material-ui/core/TableHead';
    import TableRow from '@material-ui/core/TableRow';
    import {Pagination} from "antd";
    
    import {getBranchList} from '../../../../../../actions/Branch';
    
    import {useDispatch, useSelector} from "react-redux";
    import CardBox from "../../../../../../components/CardBox";
    
    
    let id = 0;
    
    function BranchList() {
    
      //  react hook에서 state을 만들려면 useState을 사용
      const dispatch = useDispatch();
      const BranchData = useSelector( state  => state.branch.data );
        const [pageSize, setPageSize] = useState(5);
    
      //    페이지 변경시 이벤트
      const onChangePage = ( page ) => {
    
          let param = {
              pageStart: ( ( page - 1 ) * pageSize )
          }
          dispatch(getBranchList( param ));
      };
    
      useEffect(( )=> {
    
        dispatch(getBranchList( ));
    
        {/*
        Axios.get('/admin/react/brandList' )
            .then( response => {
              console.log( response.data );
              setBranchList( response.data )
            })
        */}
      }, [])
    
        let totCount = 0;
        let renderList = [];
        if( BranchData && BranchData.brandList && BranchData.totCount ) {
            totCount = BranchData.totCount;
            renderList = BranchData.brandList.map( ( brand, index ) => {
                let isUseBranch = brand.is_use === 'Y' ? '사용': '미사용';
                let delimit = ( brand.operate_start_time == '' || brand.operate_start_time == null ) ? '' : '~';
                return <TableRow key={index}>
                    <TableCell>{brand.brand_cd}</TableCell>
                    <TableCell align="right">{brand.brand_nm}</TableCell>
                    <TableCell align="right">{brand.brand_group_nm}</TableCell>
                    <TableCell align="right">{brand.operate_start_time} {delimit} {brand.operate_end_time}</TableCell>
                    <TableCell align="right">{isUseBranch}</TableCell>
                </TableRow>
            } );
        }
    
        const text = "브랜드 리스트"
    
      return (
    
          <div className="col-12">
              <CardBox styleName="col-12" cardStyle="p-0" heading={text} headerOutside>
              <div className="table-responsive-material">
                <Table>
                  <TableHead>
                    <TableRow>
                      <TableCell><b>브랜드코드</b></TableCell>
                      <TableCell align="right"><b>브랜드명</b></TableCell>
                      <TableCell align="right"><b>브랜드그룹</b></TableCell>
                      <TableCell align="right"><b>영업시간</b></TableCell>
                      <TableCell align="right"><b>사용여부</b></TableCell>
                    </TableRow>
                  </TableHead>
                  <TableBody>
                    {renderList}
                  </TableBody>
                </Table>
    
    
              </div>
            </CardBox>
            <div className="col-12" style={{justifyContent: "center", alignItems: "center" , display: 'flex'}}>
              <Pagination onChange={onChangePage} defaultCurrent={1} current={1} total={totCount} pageSize={pageSize}/>
            </div>
         </div>
    
    );
    }
    
    
    export default BranchList;
    

     

     

     

Designed by Tistory.