[Redux] Redux-Saga 기초 연습

mokyoungg·2020년 10월 10일
2

Redux

목록 보기
7/7

Redux-Saga를 활용한 네트워크 요청.
redux-saga의 흐름을 이해하기 위해 작성.

공식페이지의 튜토리얼을 추천.
https://mskims.github.io/redux-saga-in-korean/introduction/BeginnerTutorial.html



1. redux-saga 설치

$ npm install --save redux-saga


2. 앱과 redux-saga 연결.(src/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 reducers from './reducers'
import App from './components/App'

2. redux-saga를 위한 세팅
import createSagaMiddleware from 'redux-saga'
import rootSaga from './sagas'

3. sagaMiddleware 선언(미들웨어로 사용)
const sagaMiddleware = createSagaMiddleware();

4. redux의 store 생성, 리듀서와 미들웨어 사용
const store = createStore(reducers, applyMiddleware(sagaMiddleware)

5. 항상 store 보다 아래에서 코드가 작성되어야 한다. rootSaga를 인자로 둔다.
sagaMiddleware.run(rootSaga)

6. 
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,  
  document.querSelector('#root')
)

3. api 작성(src/apis/jsonPlaceholder.js)

import axios from 'axios'

export default axiox.create({
  baseURL: "http://jsonplaceholder.typicode.com",
})

코드를 연습하기 위해 jsonPlaceholder의 API를 사용함.
출처 : http://jsonplaceholder.typicode.com


4. Reducer 작성(src/reducers/index.js)

1. redux에서 combineReducers 가져옴
import { combineReducers } from 'redux'

2. postsReducer라는 reducer 만들기
const postsReducer = (state = [], action) => {
  switch (action.type) {
  //action의 type이 'FETCH_POSTS'라면
    case "FETCH_POSTS":
    // 그 action의 payload를 state에 할당한다.
      return action.payload;
    default:
      return state;
  }
}

3. combineReudcers로 리듀서들을 묶음. 2-1에서 사용된다. (이 코드에선 리듀서가 1개)
export default combineReducers({
  posts: postsReducer,
})

5. action 작성(src/actions/index.js)

1. fetchPostsRequest 라는 action creator 
export const fetchPostsRequest = () => {
  return {

    // 2."FETCH_POSTS_REQUEST" 라는 action을 반환한다.
    type: "FETCH_POSTS_REQUEST",
  }
}
  • action creator가 반환하는 action "FETCH_POSTS_REQUEST" 는 4번에서 제작된 reducer의 타입과 일치하지 않음.
  • 리듀서에 영향을 미치지 않는 action이다.
  • 이 action은 saga에 필요한 action이다.

6. saga 작성(src/sagas/index.js)

1. api 주소 가져오기, redux-saga의 이펙트 가져오기
import jsonPlaceholder from "../apis/jsonPlaceholder"
import { takeEvery, call, put, all } from 'redux-saga/effects'

2. fetchData(네트워크 요청) 함수 작성
const fetchData = async () => {
  //api에 get요청
  return await jsonPlaceholder.get('/posts');
}

3. fetchPosts 라는 제너레이터 함수 선언
export function* fetchPosts(){
  //fetchData를 호출(call)하고 그 결과를 response에 할당한다.
  const response = yield call(fetchData)
  // { type: 'FETCH_POSTS', payload: response.data }를 store로 put(dispatch) 한다.
  yield put({ type: 'FETCH_POSTS', payload: response.data })
}

4. watchFetchRequest 라는 제너레이터 함수 선언
export function* watchFetchRequest() {
  // takeEvery 이펙트를 사용
  // 'FETCH_POSTS_REQUEST' 라는 액션이 store에 반환되면, 
  // fetchPosts 함수를 실행한다.(3번에서 작성한 코드) 
  yield takeEvery('FETCH_POSTS_REQUEST', fetchposts)
}

5. all을 사용하여 saga들을 rootSaga로 묶는다.
// 이는 2-5 코드에서 사용된다.
export default function* rootSaga() {
  yield all([ watchFetchRequest()])
}

코드 순서 1 - 4 - 3 - 2 - 5

  • 필요한 이펙트들과 api 주소를 가져온다.
  • 'FETCH_POSTS_REQUEST' 라는 액션이 store에 반환되면,
  • fetchPosts 함수가 실행된다.
  • fethcPosts 함수 실행.(3번)
  • fetchData 함수를 호출한다.(call)
  • fetchData 함수 실행되고 그 결과를 반환한다.(2번)
  • 반환된 값을 response에 할당한다.(3번)
  • { type: 'FETCH_POSTS', payload: response.data }의 형태로
  • store에 put(dipatch) 한다.
  • dispatch가 된 것은 4-2, reducer에 의해 평가된다.

7. App 작성(src/components/App.js)

1. react와 redux를 연결하기 위한 connect와 
// fetchPostsRequest 라는 action creator를 가져온다.
import React from "react";
import { connect } from "react-redux";
import { fetchPostsRequest } from "../actions";

2.
class App extends React.Component {
  // component 가 마운트 될 때
  componentDidMount() {
    //fetchPostsRequest 라는 action creator를 호출.
    this.props.fetchPostsRequest();
  }

3.컴포넌트에서 실제로 보여지는 코드 (받은 데이터에 map을 사용한다.)
  renderList(){
    return this.props.posts.map((post) => {
      reutrn (
        <div key-{post.id}>
          <div>
            <h2>{post.title}</h2>
            <p>{post.body}</p>
        </div>
      )
    })
  }

4. 컴포넌트에 return
  render() {
    return <div>{this.renderList()}</div>
  }

5. mapStateToProps 작성
const mapStateToProps = (state) => {
  return { posts: state.posts };
};

6. connect를 사용하여 react의 컴포넌트가 redux의 store에 연결
export default connect(mapStateToProps, { fetchPostsRequest })(App);

코드 순서 1 - 5 - 6 - 2 - 3 - 4
bold 처리한 숫자는 섹션별 코드를 뜻함.
4 = Reducer 작성, 5 = action 작성, 6 = saga 작성

  1. 컴포넌트(App)의 생성과 redux store에 연결하기 위해 필요한 것들을 가져온다.(1번 코드)

  2. mapStateToProps를 작성하여 store에 있는 모든 state를 가져올 준비를 한다.(5번 코드)

  3. 모든 state 중 posts라는 키의 값을 가져오고, (state 내부의 key(posts))

  4. 그 값을 posts 라는 key에 할당한다. (컴포넌트에서 사용하기 위해 설정한 key(posts))

  5. connect함수로 App 컴포넌트와 redux store를 연결한다.(6번 코드)

  6. 이때 mapStateToProps를 첫번째 인자로,

  7. fetchPostsRequest 라는 action creator 를 두번째 인자로 둔다.

  8. 이제 App 컴포넌트는 this.props 를 통해 store에서

  9. state와 action creator를 가져올 수 있게 된다.

  10. 이제 컴포넌트가 componentDidMount가 되면(2번 코드)

  11. this.props.fetchPostsRequest() 를 통해, store에 있는 fetchPostsRequest라는 액션생성자를 호출한다.

  12. 액션생성자 호출을 통해, 해당 액션생성자는 "FETCH_POSTS_REQUEST" 라는 액션을 반환한다.
    5번 action 작성 코드의 2번 코드 참고(5-2)

  13. Redux-Saga 발동

  14. watchFetchRequest 함수가 작동한다.(6-4) ("FETCH_POSTS_REQUEST"를 takeEvery 했기 때문)

  15. fetchposts 함수 실행이 되고 call을 통해 함수를 호출한다. (6-3)

  16. fetchData 함수 실행(네트워크 요청) (6-2)

  17. 네트워크의 요청의 값을 response에 할당(6-3)

  18. { type: 'FETCH_POSTS', payload: response.data }의 형태로 store에 put(dipatch) 한다.(6-3)

  19. Redux-Saga 종료

  20. put 된 객체를 reducer에서 평가한다. (4-2)

  21. action.type === 'FETCH_POSTS' 가 참(true)이므로 객체의 payload 값을 state에 할당한다.

  22. reducer에서 할당된 state를 mapStateToProps를 통해 받는다.(posts라는 key 이름으로)

  23. this.props.posts의(데이터) 형태는 배열이고 이를 map 메서드를 활용하여 component에서 보이게 한다.


8. 정리

component에서 action creator가 호출

action이 store로 이동

saga의 effect인 takeEvery(Latest) 가 이를 확인

saga에서 작성한 제너레이터 함수들이 작동(네트워크 요청 등)

saga의 제너레이터 함수가 put을 통해 store로 결과 값을(순수한 객체) dispatch 함.

reducer에선 이를 확인하여 state값 변경

변경 된 state를 통해 component가 render 됨.



네트워크 요청시, redux-saga의 흐름을 이해하기 위해 작성한 코드이다.
위의 코드에선 call 이펙트의 인자 활용은 하지 않았다.
그리고 네트워크 요청의 비동기 처리를 위해 작성한 async/awiat 코드는 확실하지 않다.(작동은 함)

위 코드에선 라이프 사이클인 componentDidMount()를 활용하여 action creator를 호출하였지만
onClick 이벤트를 통해 action creator를 호출도 가능하다.(당연한 얘기이다.)

아직 멀었다.

profile
생경하다.

0개의 댓글