728x90

JavaScript와 HTML,CSS등에 대해서는 일체 다루지 않는다.

기초 지식은 다른 강의를 참조하도록 하라.

이 강의는 React의 사용법 위주로 작성되어 있다.

왜 React여야는지, React를 써야하는지에 대하지는 논하지 않으므로 그런 자료는 다른 데이터를 참조하라.


참조:

react docs 


redux(포스팅 참조)를 사용하고 react-redux(포스팅참조)를 사용해서 좀 더 flux하게 만들었다.

하지만 이 구조에서 비동기콜을 쓰기는 까다로운 면이 있는데 일을 해결하기 위해서 redux에서 사용하는 이벤트 핸들러 redux-saga가 등장했다.

react는 프론트엔드이고 필연적으로 restapi나 graphql콜을 사용해서 페이지를 그리게된다.

그러므로 react에서 redux를 사용한다면 redux-saga는 거의 필연적이게 사용되게 된다.


https://mskims.github.io/redux-saga-in-korean/ 

위 사이트에서 많은 도움을 얻을 수 있다.


https://github.com/justkukaro/react-test-project/tree/master/redux-saga-test

이번에 보여줄 페이지는 예제가 좀 큰데 그래서 예제 프로젝트를 링크한다.


예제에서 통신하는 예제 서버는 위와 같다. 그냥 프로젝트 설치한 후 npm install 하고 npm run start하면 돌아간다.
물론 여러분이 사용하는 서버와 연결해도 무방하다.

프로젝트 구조는 위와 같다.

사실 saga가 추가되고 store이 외부로 빠져나간걸 제외하면 큰 차이는 없는데 하나씩 보도록 하자.

전 장에서 설명했다면 설명하지 않고 넘어 가겠다.

모르는 부분이 있다면 redux나 react-redux포스팅을 참조하고 넘어가자.


npm install --save redux-saga


당연히 redux-saga의 설치는 위 처럼 한다.


// actions/index.js
export const CALL_DATA = 'CALL_DATA';
export const CALL_DATA_SUCCESS = 'CALL_DATA_SUCCESS';
export const CALL_DATA_FAIL = 'CALL_DATA_FAIL';

export const callData = () => {
return {
type: CALL_DATA
}
};

export const callDataSuccess = (data) => {
return {
type: CALL_DATA_SUCCESS,
data
}
};

export const callDataFail = () => {
return {
type: CALL_DATA_FAIL
}
};

액션(이벤트)를 정의하는데 saga구조에서는 이벤트는 saga를 거쳐가야하기 때문에 saga용 이벤트를 만든다.

무슨 소리냐 하면 이벤트,이벤트성공,이벤트실패로 총 3개를 만들어야한다. 위의 예제에도 callData,callDataSuccess,callDataFail로 총 3가지를 만들었다.

saga구조를 거칠 필요성이 없다면 기존과 같은 방식으로 제작해도 무방하다.


callDataSuccess만 data를 추가로 받는데 성공했으니까 데이터 넘겨줘야해서 그렇다.


// reducers/index.js
import {CALL_DATA, CALL_DATA_SUCCESS, CALL_DATA_FAIL} from '../actions'
import {combineReducers} from 'redux'

const initState = {
number: 0,
};

const data = (state = initState, action) => {
switch (action.type) {
case CALL_DATA:
return {...state};
case CALL_DATA_SUCCESS:
state.number = action.data['num'];
return {...state};
case CALL_DATA_FAIL:
return {...state};
default:
return {...state};
}
};

const App = combineReducers({
data
});

export default App;

여기서도 CALL_DATA는 아무것도 하지 않는다. 어짜피 saga로 넘어가서 처리할 이벤트이기 때문.

물론 뭔가를 해도 상관은 없다.

그리고 성공헀을 때는 받은 데이터로 state를 변경시켜준다.

실패시해도 뭔가를 할 수 있지만 그리 하지는 않았다.


//saga/index.js
import { call, spawn, put, takeEvery } from "redux-saga/effects";
import axios from "axios";
import {CALL_DATA, callDataFail, callDataSuccess} from "../actions";

function* fetchCallData() {
const { data } = yield axios.get("http://localhost:4321");
try {
yield put(callDataSuccess(data));
} catch (error) {
yield put(callDataFail(data))
}
}

function* watchCall() {
yield takeEvery(CALL_DATA, fetchCallData);
}

export default function* root() {
yield spawn(watchCall);
}

대망의 saga이다. saga의 함수들은 제네레이터인데 크게 보면 두종류가 필요하다.


이벤트를 감지하는 watch와 그 때 적용할 콜백인 fetch이다.

물론 공식이름은 아니고 여러분이 다른 이름으로 사용해도 무방하다.


function* watchCall() {
yield takeEvery(CALL_DATA, fetchCallData);
}

watchCall에서 takeEvery를 선언했다.

이는 더 종류가 있긴한데 CALL_DATA형으로 발생하는 모든 이벤트를 받아서 fetchCallData를 실행한다.

즉 이 녀석은 이벤트 감시자이다. 그래서 이름이 watch인 것.


function* fetchCallData() {
const {data} = yield axios.get("http://localhost:4321");
try {
yield put(callDataSuccess(data));
} catch (error) {
yield put(callDataFail(data))
}
}

그러면 이 녀석이 호출된다.

이 녀석은 axios로 값을 받아온다.

그리고 이 녀석이 제대로 값을 가져왔다면 put으로 callDataSuccess(우리가 정의한)를 던지고

제대로 값을 못 가져왔다면 put으로 callDataFail(마찬가지로 우리가 정의한)을 던진다.

사실 callDataFail은 data를 못받게 우리가 만들었기 때문에 던져봤자 받는게 불가능하다.


//store/index.js
import createSagaMiddleware, { END } from "redux-saga";
import {applyMiddleware, compose, createStore} from "redux";
import rootReducer from '../reducers';
const saga = createSagaMiddleware();

export function configureStore(initialState) {
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(saga),
)
);

store.runSaga = saga.run;
store.close = () => store.dispatch(END);

return store;
}

그리고 기존에 우리가 예제에서 store가 index.js내부에 존재했는데 이를 뺀다.

그리고 미들웨어에 saga를 등록시켜준다.

saga.run을 store.runSaga에 해줘야 saga가 동작한다.

여기서 끝난건 아니고 index.js도 건드려줘야한다.


// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';
import {Provider} from 'react-redux'
import saga from './saga'
import {configureStore} from './store'

const store = configureStore();
store.runSaga(saga);

const render = () => {
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
};
store.subscribe(render);
render();

여기서 코드를 보면 짧막하게 바뀐게 있다.


const store = configureStore();
store.runSaga(saga);

외부에서 선언한 store를 선언해 주고 runSaga로 실행해준다.


제대로 동작하는걸 확인할 수 있다. 예제는 해당 서버에 랜덤 숫자를 불러와서 화면에 뿌려주는 예제이다.


뭐 당연하지만 redux-saga를 사용하지 않아도 된다.

그러나 비동기를 관리하거나 이벤트 핸들러를 부착하고싶다면 redux-saga는 매우 유용한 플러그인일 것이다.


+ Recent posts