[React] react-saga, react-thunk (redux-toolkit) 에 대해서

    728x90
    반응형

     

     

     

    Redux만 있으면 안되나?

    Redux는 자바스크립트 앱을 위한 상태 관리 라이브러리로, 주로 React와 함께 사용됩니다.

    그러나 Redux 자체는 비동기 작업을 처리할 수 없기 때문에,

    Redux Thunk나 Redux Saga와 같은 미들웨어를 사용하여 비동기 로직을 처리합니다.

    이 두 미들웨어는 각각 특징과 사용 방법이 다릅니다.

    *미들웨어 = 리덕스의 기능을 향상시켜주는 것

     

     

    Redux-Thunk?

    Redux Thunk는 Redux 애플리케이션에서 비동기 작업을 관리하기 위한 가장 기본적인 미들웨어입니다.

    Thunk는 함수를 반환하는 함수로, Redux에서 비동기 작업을 처리할 때 사용됩니다.

     

    고차함수, 삼단함수 입니다.
    리덕스가 비동기 액션을 디스패치할 수 있도록 도와준다.  thnk는 지연된 함수라는 프로그래밍 용어
    예) 비동기 디스패치 보내고 성공하면 어떤 디스패치, 실패하면 어떤 디스패치 가능 ---> 리덕스 기능 확장
     @reduxjs/toolkit에는 이미 redux-thunk가 내장되어있다. --> createAsyncThunk로 액션 만들어서 쓰면 된다.
    간단한거할땐 thunk 복잡해지면(추가적인 기능이 필요하면) saga추천 

     

    특징:

    • 간단한 구현: Thunk는 비교적 이해하기 쉽고 구현하기 간단합니다.
    • 단순한 기능: 비동기를 처리해주는 기능만 내포하고 다른 기능은 없다.
    • 함수 반환: Action 대신 함수를 반환할 수 있게 하여, 그 함수 안에서 비동기 API 호출을 실행하고 다른 액션을 디스패치할 수 있습니다. 즉 하나의 비동기 액션 안에서 여러가직 동기액션 실행할 수 있다.
    • 비동기 액션 크리에이터 만들 수 있다. => 호출하면 비동기 함수 리턴. 그걸 호출하면 실행

     

    기본 예시

    const fetchUserData = () => {
      return (dispatch, getState) => {
      // 동기 디스패치 실행
        dispatch({ type: 'LOADING_USER_DAT A' }); 
        // 비동기 함수 실행
        fetch('/api/user/data') 
          .then(response => response.json())
          // 동기 디스패치 실행
          .then(data => dispatch({ type: 'SET_USER_DATA', payload: data }))
          // 동기 디스패치 실행
          .catch(error => dispatch({ type: 'ERROR_LOADING_DATA', error }));
      };
    };

     

     

    redux-toolket 의 createAsyncThunk 사용 예시

     

    /components/loginForm.js

    // reducuer에서 만든 thunk 함수를 호출
    const onSubmitForm = useCallback(() => {
        dispatch(logIn({ email, password }));
      }, [email, password]);

     

    /reducers/user.js

    import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
    import axios from 'axios';
    import { HYDRATE } from 'next-redux-wrapper';
    
    // toolkit에 내장된 thunk 만들기
    export const logIn = createAsyncThunk('user/logIn', async (data) => {
      const response = await axios.post('/user/login', data);
      return response.data;
    });
    
    const userSlice = createSlice({
      name: 'user',
      initialState,
      reducers: {
        test(state, action) {
          console.log(state, action);
        },
      },
      extraReducers: (builder) => builder
        .addCase(HYDRATE, (state, action) => ({
          ...state,
          ...action.payload.user,
        }))
        // 만든 thunk가 실행될때 관련 리듀서들 작동
        .addCase(logIn.pending, (state) => {
          state.logInLoading = true;
          state.logInError = null;
          state.logInDone = false;
        })
        .addCase(logIn.fulfilled, (state, action) => {
          state.logInLoading = false;
          state.me = action.payload;
          state.logInDone = true;
        })
        .addCase(logIn.rejected, (state, action) => {
          state.logInLoading = false;
          state.logInError = action.error.message;
        })
    })

     

     

     

     

     

     

    Redux Saga?

    Redux는 동기적으로 실행되기 때문에 비동기적 요청을 하기 위해서

    Redux Saga의 여러 메소드를 사용하기 위해서

    thunk처럼 하나의 액션안에서 비동기 함수를 실행시키고 동기적인 dispatch를 호출함과 동시에

    추가적인 여러가지 기능들을 사용하기 위해서.

    *간단한 작업이면 thunk, 기능이 필요하게 되면  saga!

    *next-redux-sage는 next에 내장되어있다.

     

    특징:

    • 다양한 기능(effects): Thunk와는 다르게 다양한 기능들이 있어. 필요할때마다 쓸 수 있다.
    • generator 문법: *,  yield, next()등을 사용해 중단점을 만들고 다음을 실행시키는 함수.

     

    Saga에서 사용되는 Generator?

    • 함수에 *를 붙이고, yield를 사용해 멈출 수 있다. (중단점이 생겼다)
    • 그냥 함수만 실행해선 작동하지 않는다 .next()를 해줘야 다음꺼 실행
    • 변수에 함수를 저장해놓고, 그것을 가지고 사용한다 redux-saga가 이걸 활용함.
    • 무한의 개념, 이벤트 리스너에 활용
    const num = function* () {
        console.log(1);
        yield;
        console.log(2);
    }
    
    const number = num();
    number.next(); // 1
    number.next(); // 2

    yield는 generator 함수의 실행을 일시 정지시키고 next()를 사용해서 진행할 수 있다.

     

     

    Redux Saga에서 사용하는 함수 (saga effects)

    • delay : settimeout 같은 역할, 원하는 시간만큼 지연
    • put : 액션을 dispatch
    • call : 동기 함수 호출. 다음 이벤트까지 기다려줌. (axios.post(/api/login).then(...)) 이런 느낌 결과값을 받아올때까지
    • fork : 비동기 함수 호출. yield 없는 느낌으로 기다려주지 않는다.
    • all : 인수로 배열을 받고, 배열안의 모든걸(함수) 한번에 실행 ( fork / call로 실행할 함수들 동시에 실행 )
    • take : yield take("LOG_IN", login) 여기서 LOG_IN이라는 이벤트 실행되면 login을 실행한다. 이벤트 리스너같은 느낌. 치명적인 단점으로 일회용이다. 그래서 사용하면 while(true) {} 를 감싸서 사용하는데 다른 take 함수를 쓴다. --->  *동기적으로 실행
    • takeEvery : 들어오는 모든 액션을 실행한다. *비동기적 실행
    • takeLatest : 액션이 2번이상 들어왔을 때 마지막 액션만 실행한다. 정확히는 연속으로 여러 액션이 들어왔을때 기본적으로는 마지막 액션, 이전 액션이 완료가 된 상황이라면 그 후에 호출, 그전이라면 취소하고 마지막 호출. *그렇지만 호출한 액션 수만큼 서버에 요청은 그대로가고, 요청 후 응답을 마지막꺼만 받는 식이라서 서버에서도 확인이 필요하다.
    • takeLeading : 가장 빠른 먼저 호출한 액션만 실행
    • throttle : 일정 시간동안은 이벤트 한번만 작동. (무한 스크롤처럼) 
    • debounce : 연속적으로 이벤트가 들어오면 기존 이벤트를 취소하고, 새로운 이벤트를 실행 (검색시 input 이벤트)

     

    redux-toolket 과 함께 사용한 redux-saga 사용 예시

    흐름

    1. 로그인 버튼을 클릭하면, dispatch함수로 리듀서 액션 함수 호출 (loginRequestAction) -->
    2. 리듀서의 함수를 호출하게 되면, 그 액션이 발생함으로써 saga의 login 함수 호출 (loginRequestAction,  login)  -->
    3. saga의 로그인 함수(비동기 액션)에서 비동기 함수를 호출하고, 그에맞는 reducer 액션들을 put으로 호출 ( loginSuccessAction, loginFailureAction)  -->
    4. 리듀서에서 해당 액션 호출. store의 상태 업데이트  -->
    5. 뷰화면 업데이트

     

    /reducers/user.js

    import { createSlice } from "@reduxjs/toolkit";
    
    export const userSlice = createSlice({
      name: "user",
      initialState,
      reducers: {
        loginRequestAction: (state) => {
    	  state.isLoggingIn = true;
        },
        loginSuccessAction: (state) => {
          state.isLoggingIn = false;
          state.isLoggedIn = true;
          state.me = action.payload;
          state.me.nickname = "zzimzzim";
        },
        loginFailureAction: (state, action) => {
          state.isLoggingIn = false;
          state.isLoggedIn = true;
        },
      },
    });
    
    export const {
      loginRequestAction,
      loginSuccessAction,
      loginFailureAction,
    } = userSlice.actions;
    
    export default counterSlice.reducer;

    먼저 createSlice를 이용해서 reducer, action을 만들어준다.

    하나의 동작에 위의 reducers처럼 loginRequestAction, loginSuccessAction , loginFailureAction 로 비동기를 분기 처리

    할 수 있도록 진입, 성공, 실패 3가지로 작성한다. (무조건 기본은 세가지)

     

    /store/configureStore.js

    import { combineReducers, configureStore } from "@reduxjs/toolkit";
    import createSagaMiddleware from "redux-saga";
    import { user } from "./counter";
    import rootSaga from "../sagas";
    
    const rootReducer = combineReducers({
      user,
    });
    
    const sagaMiddleware = createSagaMiddleware();
    
    const makeStore = () => {
      const store = configureStore({
        reducer: rootReducer,
        devTools: true,
        middleware: (getDefaultMiddleware) =>
    	getDefaultMiddleware().concat(sagaMiddleware),
      });
    
      sagaMiddleware.run(rootSaga);
    
      return store;
    };
    
    const wrapper = createWrapper(makeStore, {
    	// createWrapper: wrapper 생성, wrapper 에 store 바인딩
    	debug: process.env.NODE_ENV === "development", // true이면 리덕스에 관한
    });
    
    export default wrapper;

    store에 Saga를 붙여준다.

    Redux Toolkit과 큰 차이점은 없다. middleware를 추가해주는 것이다.

     

    /sagas/user.js

    import { all, fork, delay, put, takeLatest } from "redux-saga/effects";
    import axios from "axios";
    import {
    	loginRequestAction,
    	loginFailureAction,
    	loginSuccessAction,
    	logoutFailureAction,
    	logoutRequestAction,
    	logoutSuccessAction,
    } from "../reducers/user";
    
    function loginAPI(data) {
    	return axios.post("/api/login", data);
    }
    
    function* login(action) {
        try {
        // const result = yield call(loginAPI, action.data);
            yield delay(1000); // 비동기 함수라 가정
            yield put(loginSuccessAction(action.payload));
        } catch (err) {
            yield put({
                type: loginFailureAction,
                // data: err.response.data,
            });
        }
    }
    
    // loginRequestAction액션이 발생하면 login 실행
    function* watchLogIn() {
    	yield takeLatest(loginRequestAction, login);
    }
    
    // all안의 모든 함수 실행
    export default function* userSaga() {
    	yield all([fork(watchLogIn)]); // fork 함수 실행
    }

    비동기 작업을 진행하는 saga 함수를 만들어준다.

     

    /sagas/index.js

    import postSaga from "./post";
    import userSaga from "./user";
    
    export default function* rootSaga() {
    	yield all([fork(userSaga), fork(postSaga)]);
    }

    만들어 놓은 Saga함수를 하나로 만드는 rootSaga이다.

     

    /pages/_app.js

    import PropTypes from "prop-types";
    import Head from "next/head";
    import { Provider } from "react-redux";
    import wrapper from "../store/configurStore";
    
    const NodeBird = ({ Component, ...rest }) => {
        const { store, props } = wrapper.useWrappedStore(rest);
        const { pageProps } = props;
        return (
            <Provider store={store}>
                <Head>
                    <meta charSet='utf-8' />
                    <meta
                        name='viewport'
                        content='width=device-width, user-scalable=no,
                        initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0'
                    />
                    <title>NodeBird</title>
                </Head>
                <Component {...pageProps} />
            </Provider>
        );
    };

    다른 middleware과 마찬가지로 Provider를 이용해서 store를 전해주면 전역 상태를 사용할 수 있다.

     

    import { useDispatch } from "react-redux";
    import { useSelector } from "react-redux";
    import { loginRequestAction } from "../reducers/user";
    
    export default function Counter() {
      const number = useSelector((state) => state.number);
      const dispatch = useDispatch();
      const onFinish = useCallback((data) => {
    		dispatch(loginRequestAction({ id: data.userId, password: data.password }));
    	}, []);
      return (
        <div>
        </div>
      );
    }

     

     

     

     

     

     

     

    참조!

    https://choisuhyeok.tistory.com/57

     

    [Redux Saga] Redux Toolkit + Redux Saga 맛보기 (Counter 예제)

    이번에는 Redux Toolkit과 Redux Saga를 함께 사용했다. Toolkit이 잘 기억나지 않는다면 이전 글을 확인하고 와도 된다. 사실 별거 없다. https://choisuhyeok.tistory.com/54 [Redux Toolkit] 지저분한 리덕스, RTK로 정

    choisuhyeok.tistory.com

     

    728x90
    반응형

    댓글