Github-finder (2)


#Input Reducerの作成#


1)/modules/input.jsの作成
   : input reducerを作成し、usernameというステータスを作成します.
import { createAction, handleActions } from 'redux-actions';

const CHANGE_INPUT = 'input/CHANGE_INPUT';

export const changeInput = createAction(CHANGE_INPUT, (username)=>({
    username,
}));

const initState = {
    username: "",
};

const input = handleActions(
    {
        [CHANGE_INPUT]: (state, { payload: {username}}) => ({
            ...state,
            username,
        }),
    },
    initState
)

export default input;
2)rootReducerに追加
import { combineReducers } from "redux";
import { all } from "redux-saga/effects";
import input from './input';

const rootReducer = combineReducers({
  input,
});

export default rootReducer;
3) InputContainer.jsでの使用
   : Input reducerに含まれるusernameというstateを使用します.
     useDispatch()とuseSelector()の使用
import React, {useRef} from 'react';
import InputComponent from '../components/InputComponent';
import {useDispatch, useSelector} from "react-redux";
import {changeInput} from '../modules/input';

function InputContainer() {

    const dispatch = useDispatch();

    /* 입력받는 값을 실시간으로 username에 업데이트 */
    const onHandleInputChange = (e)=>{
        const value = e.target.value;
        dispatch(changeInput(value));
    };

    return (
        <>
            <InputComponent
            onHandleInputChange={onHandleInputChange}>
            </InputComponent>
        </>
    )
}

export default InputContainer

#user Reducer作成と使用#

  • ユーザReducerに必要な状態
    1)ユーザー情報
        : https://api.github.com/users/[계정이름]の応答情報を保存
    2)reposInfo(ユーザのrepo情報)
        : https://api.github.com/users/[アカウント名]/reposの応答情報
  • が記憶する.
    1)/modules/user.jsの作成
    import { createAction, handleActions } from 'redux-actions';
    import createRequestSaga from "../lib/createRequestSaga";
    import { createRequestActionTypes } from "../lib/createRequestSaga";
    import * as userAPI from "../lib/api/user";
    import { takeLatest } from "redux-saga/effects";
    
    /* GET_USER에 대한 성공,실패 액션을 생성 */
    const [GET_USER, GET_USER_SUCCESS, GET_USER_FAILURE] = createRequestActionTypes(
        "user/GET_USER"
    );
    const [GET_REPOS, GET_REPOS_SUCCESS, GET_REPOS_FAILURE] = createRequestActionTypes(
        "user/GET_REPOS"
    );
    
    /* 액션 호출 함수 생성 */
    export const getUser = createAction(GET_USER, (username) => username);
    export const getRepo = createAction(GET_REPOS, (username) => username);
    
    /* 해당하는 액션 호출시 Saga실행 */
    const getUserSaga = createRequestSaga(GET_USER, userAPI.userInfo);
    const getRepoSaga = createRequestSaga(GET_REPOS, userAPI.userRepo);
    
    /* 요청된 것들 중 가장 마지막 요청만 처리 (여러번 클릭시 모두 처리되면 매우 비효율적!) */
    export function* userSaga(){
        yield takeLatest(GET_USER, getUserSaga);
        yield takeLatest(GET_REPOS, getRepoSaga);
    }
    
    /* State 초기값 */
    const initState = {
        userInfo: null,
        error: null,
        reposInfo: null,
    }
    
    /* 액션을 store에 저장하는 리듀서를 handleActions로 쉽게 처리! */
    const user = handleActions(
        {
            [GET_USER_SUCCESS]: (state, { payload: userInfo }) => ({
                ...state,
                userInfo,
              }),
              [GET_USER_FAILURE]: (state, { payload: error }) => ({
                ...state,
                error,
              }),
              [GET_REPOS_SUCCESS]: (state, { payload: reposInfo }) => ({
                ...state,
                reposInfo,
              }),
              [GET_REPOS_FAILURE]: (state, { payload: error }) => ({
                ...state,
                error,
              }),
        },
        initState
    );
    
    export default user;
    2)rootReducerに追加
    import { combineReducers } from "redux";
    import { all } from "redux-saga/effects";
    import input from './input';
    import user, {userSaga} from './user';
    
    const rootReducer = combineReducers({
      input,
      user
    });
    
    /* Saga사용을 위해 생성한 userSaga()를 추가! */
    export function* rootSaga() {
      yield all([userSaga()]);
    }
    
    export default rootReducer;
    3) InputContainer.変更
       : フォームの作成後にフォームをコミットするときにgetUser()/getRepo()を追加
    function InputContainer() {
        const dispatch = useDispatch();
        //const nameInput = useRef();
    
        /* dispatch로 값 넘기기 위해 useSelector로 값 가져옴 */
        const {username} = useSelector(({input})=>({
            username: input.username,
        }));
    
        const onHandleInputChange = (e)=>{
            const value = e.target.value;
            dispatch(changeInput(value));
        };
         /* 추가된 부분의 핵심 */
        const onHandleFormSubmit = (e) => {
            e.preventDefault();
            dispatch(getUser(username));
            dispatch(getRepo(username));
        }
    
        return (
            <>
                <InputComponent
                onHandleInputChange={onHandleInputChange}
                onHandleFormSubmit={onHandleFormSubmit}
                username={username}
    
                </InputComponent>
            </>
        )
    }
    4) ResultContainer.変更
       : 保存したuserInfo/reposInfostateをコンポーネントに転送
    import React from 'react'
    import ResultComponent from '../components/ResultComponent';
    import { useSelector } from "react-redux";
    
    function ResultContainer() {
    
         /* 필요한 state들을 useSelector로 store에서 가져온다 */
        const {userInfo, reposInfo, loading} = useSelector(({user, loading})=>({
            userInfo: user.userInfo,
            reposInfo: user.reposInfo,
            loading: loading["user/GET_REPOS"],
        }));
    
        return (
            <>
                <ResultComponent loading={loading} userInfo={userInfo} reposInfo={reposInfo}>
                </ResultComponent>  
            </>
        )
    }
    
    export default ResultContainer
    5) ResultComponent.変更
       : 必要に応じてContainerから送信されたuserInfoとreposInfo(条件レンダリング)を出力します.
    function SearchResult({userInfo, reposInfo, loading}) {
        return (
            <>
                {
                    userInfo && 
                        <CardTemplate>
                            <Avatar src={userInfo.avatar_url}/>
                            <UserInfo>
                                <h2>{userInfo.name}</h2>
                                <p>{userInfo.bio}</p>
                                <FollowTemplate>
                                    <Follow><strong>Followers</strong> {userInfo.followers}</Follow>
                                    <Follow><strong>Following</strong> {userInfo.following}</Follow>
                                    <Follow><strong>Repos</strong> {userInfo.public_repos}</Follow>
                                </FollowTemplate>
                                <div>
                                {
                                    reposInfo ? 
                                           reposInfo.slice(0,10).map((repo, idx)=>(
                                                <Repo key={idx} href={repo.html_url} target="_blank">{repo.name}</Repo>
                                            ))
                                            : (loading == true? <Loading>LOADING</Loading>:null)
                                       
                                }
                                </div>
                            </UserInfo>
                        </CardTemplate>
                }
            </>
        );
    }
    export default SearchResult

    # loading Reducer #

  • 原理:ローダを作成し、Sagaを使用して
  • を挿入します.
  • startLoading()/finishLoading()リクエスト前/リクエスト後ステータス管理を実行!
  • 1)/modules/loading.js
       : startLoading()/finishLoading()と適切なreducer関数の作成
    import { createAction, handleActions } from "redux-actions";
    
    const START_LOADING = "loading/START_LOADING";
    const FINISH_LOADING = "loading/FINISH_LOADING";
    
    export const startLoading = createAction(
      START_LOADING,
      (requestType) => requestType
    );
    
    export const finishLoading = createAction(
      FINISH_LOADING,
      (requestType) => requestType
    );
    
    const initState = {};
    
    const loading = handleActions(
      {
        [START_LOADING]: (state, action) => ({
          ...state,
         [action.payload]: true,
        }),
        [FINISH_LOADING]: (state, action) => ({
          ...state,
         [action.payload]: false,
        }),
      },
      initState
    );
    
    export default loading;
    2)/lib/createRequestSaga.jsの作成
       : 以前にgetUserSaga/getRepoSagaを作成したときなどのリクエスト
         createRequestSagaを使用するため、リクエストの前後
         startLoading()/finishLoading()!
    import { put, call } from "redux-saga/effects";
    import { startLoading, finishLoading } from "../modules/loading";
    
    export const createRequestActionTypes = (type) => {
      const SUCCESS = `${type}_SUCCESS`;
      const FAILURE = `${type}_FAILURE`;
      return [type, SUCCESS, FAILURE];
    };
    
    export default function createRequestSage(type, request) {
      const SUCCESS = `${type}_SUCCESS`;
      const FAILURE = `${type}_FAILURE`;
      return function* (action) {
    
        /* 요청 전에 값을 true로 변환 */
        yield put(startLoading(type));
    
        try {
          const response = yield call(request, action.payload);
          yield put({
            type: SUCCESS,
            payload: response.data,
            meta: response,
          });
        } catch (error) {
          yield put({
            type: FAILURE,
            payload: error,
            error: true,
          });
        }
    
          /* 요청이 끝나고 값을 false로 변환 */
        yield put(finishLoading(type));
      };
    }
    3)構成部品からのロード値の取得
      : storeから値をインポートすると、trueまたはfalseの値を持つ特定のアクションにloadがインポートされます.
        : ロード状態に応じた条件付きレンダリング