티스토리 뷰
✅ 이전 포스팅에서 Redux의 기초에 대해서 알아봤었습니다. 이번 포스팅 글에서는 Redux의 미들웨어로 많이 사용되는 Redux-Saga에 대해서 알아보겠습니다. Redux에 잘 모르는 상태에서 처음으로 이 글을 접하셨다면 아래 포스팅 글을 먼저 읽고 오시는 것을 추천드리겠습니다! + 부가적으로 generator함수와 yield에 관해서도 읽는 것을 추천드립니다!
( 글을 읽기 전 redux-saga 기본 용어에 대해 읽어보시면 이해하는데 도움이 될 것 같아서 더보기란에 짧게 정리하였습니다. )
* delay : 설정된 시간 이후에 resolve 하는 Promise 객체를 리턴합니다.
* put : 특정 Action을 Dispatch 합니다.
* fork : Generator 함수를 실행합니다. ( call 과는 다릅니다. )
* takeEvery : 들어오는 모든 Action에 대해 특정 작업을 처리합니다.
* takeLatest : 기존에 진행 중이던 작업이 있다면 취소 처리하고, 가장 마지막으로 실행된 작업만 수행합니다.
* takeLeading : 첫 번째 이벤트만 실행하고, 그 후로는 무시합니다.
* call : 함수의 첫 번째 파라미터는 함수, 나머지 파라미터는 해당 함수에 넣을 인수입니다. Saga 함수 안의 로직이 동기 처리되도록 도와줍니다.
* all : Generator 함수를 배열 형태로 넣어주면, 해당 함수들이 병행적으로 동시에 실행되고, 전부 resolve 될 때 까지 기다립니다.
* throttle : - 초를 주기로 request를 1번만 실행합니다.
* debounce : -초 이내에 request를 1버만 실행합니다. (중복 호출 함수들 중 마지막)
[ 알아두어야 할 점 ]
- call은 동기함수호출 (api가 리턴할 때까지 기다립니다.), fork은 비동기 함수 호출 (안 기다리고 다음 로직으로 이동합니다.)
📌 Redux - Saga
- redux-saga는 redux middleware 라이브러리 중 하나로, Action과 Reducer 사이에서 흐름을 제어합니다.
- Action을 모니터링 하다가 Action이 발생하면 Reducer가 Action을 처리하기 전에 다양한 작업을 할 수 있습니다.
- 기존 요청 취소, 불필요한 중복 요청 방지가 가능합니다.
- 비동기 작업을 처리하는데 효과적입니다.
- 특정 Action이 발생했을 때 이에 따라 다른 Action이 Dispatch 되게 하거나, JS 코드를 실행할 수 있습니다.
📌 이제 코드로 살펴보겠습니다.
// npm
npm install redux-saga
npm install axios // => api 호출을 위해
// yarn
yarn add redux-saga
yarn add axios // => api 호출을 위해
// api 호출은 https://jsonplaceholder.typicode.com/ 사이트에서 기본으로 제공해주는 api를 사용했습니다.
1.actions, reducers, sagas 폴더를 만들고 위처럼 파일들을 만들어줍니다. 앞으로 나올 예제 확인을 위해서는 API, Components 폴더들 또한 만들어 줍니다.
더보기를 누르시면 소스코드를 확인하실 수 있습니다. (play1.js의 코드는 3.그럼 이제 사용해봅시다 쪽에 있습니다.)
// playAction.js
export const PostsRequest = () => {
return {
type: "getPosts",
}
}
// getPosts 라는 action을 반환합니다.
// API.js
import axios from "axios";
const API_END_POINT = 'https://jsonplaceholder.typicode.com';
// GET
export const callSelectAPI = (type) => {
let url = '';
if (type === 'getPosts') {
url = API_END_POINT + '/posts';
}
return axios.get(url).then((res) => {
return res
}).catch(e => ({ e }));
}
// axios를 이용한 API 호출 예제입니다. (method : GET)
// reducers - index.js
export const initalSatte = {
keyData: []
};
const reducer = (state = initalSatte, action) => {
switch (action.type) {
case "AXIOS_GET":
return {
...state,
keyData: action.payload.response
}
default:
return state;
}
};
// reducer 만들기
export default reducer;
// PlaySaga.js
import { callSelectAPI } from "../API/API"
import { takeLatest, call, put } from 'redux-saga/effects'
import * as PlaySaga from './PlaySaga'
function groupBy(arr, key) {
return arr.reduce((result, cur) => {
(result[cur[key]] = result[cur[key]] || []).push(cur);
return result
}, {});
}
export function* getPostData() {
try {
let response = yield call(callSelectAPI, 'getPosts');
let keyData = [];
if (200 == response.status && response.data.length > 0) {
keyData = Object.keys(groupBy(response.data, 'userId'));
}
yield put(Object.assign({}, {
type: "AXIOS_GET",
payload: {
response: keyData
},
}));
} catch {
} finally {
}
}
export default [
takeLatest('getPosts', PlaySaga.getPostData),
];
/*
[ yield call ]
함수의 첫 번째 파라미터는 함수, 나머지 파라미터는 해당 함수에 넣을 인수입니다.
주어진 함수를 실행하게 되는 것입니다.
[ yield put ]
특정 액션을 dispatch하도록 합니다.
[ takeLatest ]
기존에 진행 중이던 작업이 있다면 취소 처리하고 가장 마지막으로 실행된 작업만 수행하도록 처리해줍니다.
즉 'getPosts' 액션에 대해서 기존에 진행 중이던 작업이 있다면 취소 처리하고, 가장 마지막으로 실행된 작업에 대해서만 Saga함수를 실행한다
*/
// rootSaga.js
import { all } from 'redux-saga/effects';
import PlaySaga from './PlaySaga';
export default function* rootSaga() {
yield all([...PlaySaga]);
}
// all은 배열 안에 있는 것들을 한번에 실행해줍니다.
// all함수를 사용해서 제너레이터 함수를 배열의 형태로 인자로 넣어주면,
// 제너레이터 함수들이 병행적으로 동시에 실행되고,
// 전부 resolve될때까지 기다립니다. Promise.all과 비슷한 개념입니다.
2. 위에서 만든 Reudx-Saga를 적용시킵니다.
// 최상단의 index.js
// 1. react와 redux 사용을 위한 세팅
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux'
import './index.css';
import App from './App';
import reducer from './reducers'
// 2. redux - saga를 위한 세팅
import createSagaMiddleware from 'redux-saga';
import rootSaga from './sagas/rootSaga';
// 3. sagaMiddleware 선언(미들웨어로 사용)
const sagaMiddleware = createSagaMiddleware();
// 4. redux의 store 생성, 리듀서와 미들웨어 사용
const store = createStore(reducer, applyMiddleware(sagaMiddleware));
// 5. 항상 store 보다 아래에서 코드가 작성되어야 한다.rootSaga를 인자로 둔다.
sagaMiddleware.run(rootSaga);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
기존 Redux의 store를 선언할 때와 비교해보면 createSagaMiddleware로 미들웨어를 만들어 applyMiddleware로 적용시키고 rootSaga를 넣어서 해당 Saga를 적용할 것이라고 알려주는 것이 추가됩니다.
3. 그럼 이제 redux-saga 를 사용해봅시다!
/* App.js 하위 컴포넌트를 정의하고 렌더링해봅시다.
App.js ex))
const App = () => {
return (
<div class="App">
<body className="App-body">
<Play1 />
</body>
</div>
);
};
*/
// App.js 하위 컴포넌트
import React, { useState, useEffect } from "react";
import { PostsRequest } from "../actions/playAction";
import { connect } from "react-redux";
const Play1 = (props) => {
useEffect(() => {
props.PostsRequest();
}, []);
return (
<React.Fragment>
<section>
{
props.keyData.map((dr) => {
return <p>{dr}</p>
})
}
</section>
</React.Fragment>
)
}
let mapStateToProps = (state) => {
return {
keyData: state.keyDataa
}
}
let mapDispatchToProps = (dispatch) => {
return {
PostsRequest: () => dispatch(PostsRequest()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Play1);
기존 Redux를 적용시켰을 때와 비교해보면 별 다른 차이점이 없습니다. 하지만 내부 데이터 흐름이 다릅니다. 결과 확인 후 자세히 보겠습니다.
4. 결과 확인!
5. 마지막으로 데이터 흐름을 확인해보겠습니다.
- 디버거와 콘솔을 찍으면서 확인해봅시다.
- mapDispatchToProps에서 정의했던 PostsRequest 함수를 호출하면
- PostsRequest 액션 함수가 호출됩니다. PostsRequest함수는 "getPosts"이라는 tpye을 리턴하여 액션이 발생하고
- redux-saga에서는 이를 감지하고 액션에 해당하는 동작 getPostData 함수를 호출합니다.
- getPostData 함수는 yield call을 만나 함수의 리턴이 있을 때까지 기다립니다.
- 즉 callSelectAPI 함수를 호출하고 callSelectAPI 함수는 axios.get()을 통해 받아온 데이터를 리턴하면 다음 동작을 수행합니다.
- yield put을 만나 새로운 액션에 대해 Dispatch 합니다.
- reducer에 액션에 대한 state 변경이 있으므로 이를 수행합니다.
- 변경된 state는 mapStateToProps를 통해서 컴포넌트에 전달되고 우리는 최종적으로 4번의 결과 화면이 렌더링 된 것을 확인할 수 있습니다.
❓ saga 미들웨어가 존재하기 때문에 reducer가 아니라 먼저 rootSaga를 검사하여 type이 일치하는 함수를 호출하는 것으로 알고 있는데 콘솔 결과창 중간 2번 다음에 왜 6번이 왜 찍혔나요? (즉 rootSaga 보다 reducer를 왜 먼저 검사했느냐)
=> saga 미들웨어가 존재한다고해서 rootSaga부터 검사하는 것이 아닌 항상 reducer를 처음으로 검사하고 그다음으로 rootSaga를 검사합니다. 따라서 로직이 꼬이지 않도록 타입을 잘 정리해 둘 필요가 있습니다.
❓ 근데 제너레이터 함수는 yield를 만나면 멈추고 .next() 함수로 재개하는 거 아녔나요?
=> 맞습니다. 하지만 action을 통해 제너레이터 함수를 호출하면 함수 내 yield 부분들을 자동으로 차례대로 호출해줍니다.
🔗 참고한 글
'JavaScript > React' 카테고리의 다른 글
React - Virtual Scroll 직접 구현해보기 (part. 1) (0) | 2022.08.23 |
---|---|
React - Styled Component (0) | 2022.08.22 |
React - 상태관리 Redux 기초 (0) | 2022.08.02 |
React - 초기 렌더링 속도를 개선해보자! - [ 1편 ] (4) | 2022.07.21 |
React - setState()에 async / await이 동작할까 ? (0) | 2022.07.07 |
- Total
- Today
- Yesterday
- 시맨틱 웹
- 자바스크립트 비동기 동작원리
- array
- vue
- Next.js
- debouncing
- 타입스크립트
- next.js에 .gitignore가 적용되지 않을 때
- react
- typescript
- 1급 객체
- 1급 함수
- zustand
- 함수형 컴포넌트
- 가상스크롤
- next.js 환경변수
- 1급 시민
- redux
- Virtual Scroll
- 호이스팅
- 매겨변수와 인자
- 자바스크립트 동작원리
- redirects
- 목표 일기
- useRef
- javascript
- programmers
- 렌더링 속도 개선
- rewrites
- React로 쓰로틀링 디바운싱 구현
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |