본문 바로가기

Redux

20210617 Redux03 : Redux 개요 와 Component를 만드는 과정 정리, Async Action with Redux(MiddleWare 없이)

Redux 03





redux 개요 정리

  • 리덕스는 앱에서 사용하는 state, props를 독립적으로 사용하기 위해서 데이터를 처리하는 역할을 함
    • 데이터를 모두 받아 가지고 있기(독립적인 관리를 위해서)
    • 수정 요청있으면 수정하기
    • 필요한 위치에 보내기
  • 리덕스에서 데이터를 가져오는건 useSelector
  • 데이터를 넣어 리덕스에게 수정요청은 dispatch
    • ActionCreator가 데이터를 받아 Action으로 만들고
    • 해당 Action에는 데이터 가공 방식과 해당 데이터가 들어 있다.
    • 이를 dispatch가 받아 전달하여 reducer가 store의 state를 변경함
    • Component - props(state or function) - Container(ActionCreator - Action - dispatch) - reducer - storeState 변경



Async Action with Redux (미들웨어 없이 리덕스를 통한 비동기 액션)


리덕스를 통한 Component를 만들때 전체적인 과정


1) 사용할 데이터 구조 정하기

2) actions에 actionCreator와 type 만들기

3) 사용할 reducer 작성

4) reducerCombine 추가

5) container 만들기

6) component 만들기


사용할 데이터 구조 정하기


  • Github Users API
  • github의 Users API를 비동기적으로 가져올 때 사용할 actions를 만들어야 함
    • 비동기적으로 가져오기 시작했음을 알리는 action -> start -> loading
    • 비동기적으로 가져오기가 끝나고 성공했음을 알리는 action -> success -> data
    • 비동기적으로 가져오다가 실패했음을 알리는 action -> fail -> error
  • loading, data, error 이 3가지 데이터를 사용하기로 정함
  • 데이터 기본값 형태 : {loading: false, data: [], error: null}

ActionCreator와 Type 만들기


  • Success와 Fail의 경우 액션생성시 data, error를 받아 Action을 생성하고 reducer로 넣어 users라는 묶음으로 묶어 store의 state를 변경함
// users

// Types
export const GET_USERS_START = "GET_USERS_START"; // 호출 시작
export const GET_USERS_SUCCESS = "GET_USERS_SUCCESS"; // 추출 성공
export const GET_USERS_FAIL = "GET_USERS_FAIL"; // 추출 실패

// ActionCreator
export function getUsersStart() {
  return {
    type: GET_USERS_START,
  };
}

export function getUsersSuccess(data) {
  return {
    type: GET_USERS_SUCCESS,
    data,
  };
}

export function getUsersFail(error) {
  return {
    type: GET_USERS_FAIL,
    error,
  };
}

users Reducer 만들기

  • action type에 따라 어떻게 변경할 건지 작성

// get Types
import { GET_USERS_FAIL, GET_USERS_START, GET_USERS_SUCCESS } from "../actions";

// users
const initialState = {
  loading: false,
  data: [],
  error: null,
};

export default function users(state = initialState, action) {
  if (action.type === GET_USERS_START) {
    return {
      ...state,
      loading: true,
      error: null,
    };
  }

  if (action.type === GET_USERS_SUCCESS) {
    return {
      ...state,
      loading: false,
      data: action.data,
    };
  }

  if (action.type === GET_USERS_FAIL) {
    return {
      ...state,
      loading: false,
      error: action.error,
    };
  }

  return state;
}

reducerCombine 연결


  • 만든 reduer를 하나로 모으기 위해서 reducerCombine에 연결
import { combineReducers } from "redux";
import todos from "./todos";
import filter from "./filter";
import users from "./users";

const reducer = combineReducers({
  todos,
  filter,
  users,
});
export default reducer;

container 작성


  • 본격적으로 Component에서 사용할 함수 및 변수를 지정하여 store에 연결함
  • users로 전달할 때 storeState - users - data를 받아서 보냄
import { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import UserList from "../components/UserList";
import { getUsersFail, getUsersStart, getUsersSuccess } from "../redux/actions";

export default function UserListContainer() {
  // store에서 데이터를 가져오는 경우
  const users = useSelector((state) => state.users.data);

  // store에 데이터를 넣거나, 수정
  const dispatch = useDispatch();

  const start = useCallback(() => {
    dispatch(getUsersStart());
  }, [dispatch]);

  const success = useCallback(
    (data) => {
      dispatch(getUsersSuccess(data));
    },
    [dispatch]
  );
  const fail = useCallback(
    (error) => {
      dispatch(getUsersFail(error));
    },
    [dispatch]
  );

  // 컴포넌트 출력, props 전달
  return <UserList users={users} start={start} success={success} fail={fail} />;
}



Component 작성 및 Github API 정보 가져오기(Axios)


  • user 정보를 가져오는데 실패하거나 없는 경우 화면처리
    • 조건문으로 더빨리 return 시켜 대체 JSX를 표시함

  • 비동기적으로, 웹으로 부터 데이터 가져오는 방법으로 Axios를 사용
    • npm i axios
    • axios.get('url')
    • await를 사용하기 위해서 함수안에 넣어 사용함 (async 함수로 선언)

  • 성공 및 예외 처리를 하기 위해서 try, catch 문을 사용함
    • axios 호출 전 받아온 start 함수를 호출
    • axios 호출 후 받아온 success 함수를 user정보를 넣어 호출함 (해당 정보로, storeState - users - data에 넣음)
    • catch 문으로 error를 받아 fail 함수에 넣어 호출함 (error 정보를 storeState - users - error에 넣음)

  • useEffect를 사용하기 때문에 안에서 사용하는 props 들을 dependencies에 추가해야함 (start, success,fail)
  • 화면을 표현하는 부분 JSX는 Container에서 받은 usersData를 받아 map돌려서 차례 대로 표시함
import { useEffect } from "react";
import axios from "axios";

export default function UserList({ users, start, success, fail }) {
  // render 후 axios를 통해서 정보를 가져옴
  useEffect(() => {
    async function getUsers() {
      try {
        start();
        const res = await axios.get("https://api.github.com/users");
        success(res.data);
      } catch (error) {
        fail(error);
      }
    }
    getUsers();
  }, [start, success, fail]);

  // user 정보 없을 경우 대체 JSX
  if (users.length === 0) {
    return <p>현재 유저 정보 없음</p>;
  }
  // JSX
  return (
    <ul>
      {users.map((user) => {
        return <li key={user.id}>{user.login}</li>;
      })}
    </ul>
  );
}



비동기 작업 Container로 옮기기


  • 위와 같이 비동기 작업을 component에서 하게 된다면, 원래 template의 목적으로 분리한 의미가 사라지게 되고 Component가 복잡해짐
  • 그래서, 비동기 작업 Container에서 미리 모두 하여 하나의 함수로 만들어 해당 함수를 prop으로 Component로 보내어 사용함

Container에서 비동기 작업 함수 만들기


  • 기존에는 비동기 작업을 Component에서 했기 때문에, 비동기 작업을 위해 필요했던 함수를 Container에서 작업해서 Component로 보내어 비동기 작업을 위해 조합 했었음
  • 하지만, 비동기 작업을 Container에서 하기 때문에 굳이 Component로 보내기 위해서 dispatch로 해당 함수를 만들 필요 없이 비동기 작업 함수에 바로 dispatch를 사용하여 비동기 작업을 하는 함수 하나로 만들어 Component로 보냄
    • 즉, dispatch를 통해서 해당 비동기 작업 함수를 만들면 됨
  • 비동기 작업의 함수도 새로 생성되어 보내지기 때문에 useCallback으로 감싸 만들어야 함
  • 역시 await를 사용하기 위해서 아래와 같이 async를 사용함
import axios from "axios";
import { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import UserList from "../components/UserList";
import { getUsersFail, getUsersStart, getUsersSuccess } from "../redux/actions";

export default function UserListContainer() {
  const users = useSelector((state) => state.users.data);
  const dispatch = useDispatch();

  // 비동기 작업 함수
  const getUsers = useCallback(async () => {
    try {
      dispatch(getUsersStart());
      const res = await axios.get("https://api.github.com/users");
      dispatch(getUsersSuccess(res.data));
    } catch (error) {
      dispatch(getUsersFail(error));
    }
  }, [dispatch]);

  return <UserList users={users} getUsers={getUsers} />;
}

Component로 보낸 비동기 작업 함수 사용하기


  • 기존의 start, success, fail prop은 없어지고 getUsers 딱 하나로 합쳐짐
  • useEffect에서 props로 받아온 getUsers가 사용되어 지기 때문에 dependency에 추가함
import { useEffect } from "react";

export default function UserList({ users, getUsers }) {
  useEffect(() => {
    getUsers();
  }, [getUsers]);

  if (users.length === 0) {
    return <p>현재 유저 정보 없음</p>;
  }
  return (
    <ul>
      {users.map((user) => {
        return <li key={user.id}>{user.login}</li>;
      })}
    </ul>
  );
}