React-Native Redux Middleware
포스트
취소

React-Native Redux Middleware

안녕하세요. Narvis2 입니다.
이번 시간에는 Redux Middleware에 대하여 알아보도록 하겠습니다.
Redux MiddlewareREST API 요청 상태를 관리하기 위해 주로 사용합니다.
Redux Middleware를 사용하면 ActionDispatch된 다음, Reducer에서 해당 Action을 받아와서 Update하기 전에 추가적인 작업을 할 수 있습니다.
자세한 건 밑에서 알아보도록 하겠습니다.

🍀 Redux Middleware

  • ActionDispatch했을 때 Reducer에서 이를 처리하기에 앞서 사전에 지정된 작업을 실행할 수 있게 해줌 즉, ActionReducer사이의 중간자
  • 특정 조건에 따라 Action이 무시되게 만들 수 있음
  • Actionconsole에 출력하거나, 서버쪽에 logging을 할 수 있음
  • ActionDispatch 되었을 때 이를 수정해서 Reducer에게 전달되도록 할 수 있음
  • 특정 Action이 발생했을 때 이에 기반하여 다른 Action이 발생되도록 할 수 있음
  • 특정 Action이 발생했을 때 특정 Javascript 함수를 실행시킬 수 있음
  • Redux Middleware는 비동기 작업을 처리할 때 주로 사용
    • 비동기 작업에 관련된 Middleware 라이브러리
      • redux-thunk
        • 함수를 기반으로 작동
        • redux-toolkit에 내장되어 있어 적용하기가 간단
        • redux에서 type을 지닌 객체가 아닌, 함수 타입을 Dispatch 할 수 있게 해줌
      • redux-saga
        • Generator를 기반으로 사용
        • 특정 경우에 특정 Action을 모니터링 할 수도 있음
        • 특정 ActionDispatch 되었을때 원하는 함수를 호출하거나, 또는 라우터를 통해 다른 주소로 이동하는 것이 가능
      • redux-observable
        • RxJs를 기반으로 작동
        • 특정 경우에 특정 Action을 모니터링 할 수도 있음
        • 특정 ActionDispatch 되었을때 원하는 함수를 호출하거나, 또는 라우터를 통해 다른 주소로 이동하는 것이 가능
      • redux-promise-middleware

☘️ Redux-Thunk

  • redux에서 비동기 작업을 처리 할 때 가장 많이 사용하는 middleware
  • 함수를 기반으로 작동
  • action객체가 아닌 함수를 Dispatch할 수 있게 해줌.
  • Redux DevTools와 함께 사용
  • 적용 👉 yarn add redux-thunk

    1️⃣ 예제 ) redux-thunk middleware 적용 > App.tsx 👇

    1
    2
    3
    4
    5
    
    import rootReducer from "./src/modules";
    import { applyMiddleware, createStore } from "redux";
    import thunk from "redux-thunk";
    
    const store = createStore(rootReducer, applyMiddleware(thunk));
    

    2️⃣ 예제 ) thunk 함수 생성 > Redux Module 에 작성 👇

    1
    2
    3
    4
    5
    6
    7
    
    // ✅ thunk 함수 생성
    export const increaseAsync = () => (dispatch: Dispatch<CounterAction>) => {
      setTimeout(() => dispatch(increase()), 1000);
    };
    export const decreaseAsync = () => (dispatch: Dispatch<CounterAction>) => {
      setTimeout(() => dispatch(decrease()), 1000);
    };
    

    설명 👉 CounterActionAnyAction으로 교체해도 상관 없음

    3️⃣ 사용 ) thunk 함수 사용 👇

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    /**
     * ✅ 현재 상태를 조회
     * 상태를 조회할 때는 state의 타입을 RootState로 지정해야함
     */
    const counter = useSelector((state: RootState) => state.counter);
    // Dispatch 함수를 가져옴 (action을 발생시켜 상태를 업데이트)
    const dispatch = useDispatch();
    
    function onIncrease() {
      dispatch(increaseAsync());
    }
    
    function onDecrease() {
      dispatch(decreaseAsync());
    }
    

☘️ Redux-Thunk + Promise

  • ⚠️ Promise를 다루는 Redux Module을 다룰 때 주의 사항

    • 1️⃣ Promise가 시작(start), 성공(success), 실패(failure)했을 때 다른 ActionDispatch 해야함
    • 2️⃣ 각 Promise마다 thunk 함수를 만들어주어야 함
    • 3️⃣ Reducer에서 Action에 따라 로딩중(loading), 결과(result), 에러(error) 상태를 변경해주어야 함

    1️⃣ 예제 ) modules/posts.tsx > Redux Module 작성 👇

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    
    import { AnyAction, Dispatch } from "redux";
    import * as postsAPI from "../api/posts"; // api/posts 안의 함수 모두 불러오기
    
    // ✅ 밑의 대문자 상수는 모두 Action Type임
    
    // 포스트 여러개 조회하기
    const GET_POSTS = "GET_POSTS"; // 요청이 시작됨
    const GET_POSTS_SUCCESS = "GET_POSTS_SUCCESS"; // 성공
    const GET_POSTS_ERROR = "GET_POSTS_ERROR"; // 실패
    
    // 포스트 하나 조회하기
    const GET_POST = "GET_POST"; // 요청이 시작됨
    const GET_POST_SUCCESS = "GET_POST_SUCCESS"; // 성공
    const GET_POST_ERROR = "GET_POST_ERROR"; // 실패
    
    /**
     * thunk 를 사용할 때, 꼭 모든 Action들에 대하여 Action 생성함수를 만들 필요는 없음
     * 그냥, thunk 함수에서 바로 action 객체를 만들어 주어도 괜찮음
     */
    export const getPosts = () => async (dispatch: Dispatch<AnyAction>) => {
      dispatch({ type: GET_POSTS });
    
      try {
        const posts = await postsAPI.getPosts();
        dispatch({ type: GET_POSTS_SUCCESS, posts });
      } catch (error) {
        dispatch({ type: GET_POSTS_ERROR, error: error });
      }
    };
    
    // ✅ thunk 함수에서도 파라미터를 받아와서 사용 가능
    export const getPost =
      (id: number) => async (dispatch: Dispatch<AnyAction>) => {
        dispatch({ type: GET_POST });
    
        try {
          const post = await postsAPI.getPostById(id);
          dispatch({ type: GET_POST_SUCCESS, post });
        } catch (error) {
          dispatch({ type: GET_POST_ERROR, error: error });
        }
      };
    
    // ✅ 초깃값
    const initialState = {
      posts: {
        loading: false,
        data: null,
        error: null,
      },
      post: {
        loading: false,
        data: null,
        error: null,
      },
    };
    
    // ✅ Reducer 함수
    export default function posts(state = initialState, action) {
      switch (action.type) {
        case GET_POSTS:
          return {
            ...state,
            posts: {
              loading: true,
              data: null,
              error: null,
            },
          };
    
        case GET_POSTS_SUCCESS:
          return {
            ...state,
            posts: {
              loading: false,
              data: action.posts,
              error: null,
            },
          };
    
        case GET_POSTS_ERROR:
          return {
            ...state,
            posts: {
              loading: false,
              data: null,
              error: action.error,
            },
          };
    
        case GET_POST:
          return {
            ...state,
            post: {
              loading: true,
              data: null,
              error: null,
            },
          };
    
        case GET_POST_SUCCESS:
          return {
            ...state,
            post: {
              loading: true,
              data: action.post,
              error: null,
            },
          };
    
        case GET_POST_ERROR:
          return {
            ...state,
            post: {
              loading: true,
              data: null,
              error: action.error,
            },
          };
    
        default:
          return state;
      }
    }
    

    2️⃣ 예제 ) thunk 사용 👇

    1
    2
    3
    4
    5
    6
    7
    
    const { data, loading, error } = useSelector((state) => state.posts.posts);
    const dispatch = useDispatch();
    
    // 컴포넌트 마운트 후 포스트 목록 요청
    useEffect(() => {
      dispatch(getPosts());
    }, [dispatch]);
    
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.