책 읽다가 코딩하다 죽을래

immer, redux-action 쓰는 방법 본문

코딩/react

immer, redux-action 쓰는 방법

ABlue 2021. 8. 10. 07:04

React-Redux를 더욱 쉽게 관리하게 만들어주는

immer, redux-action에 대해 배워보자

 

 

모듈 설치 방법

yarn add redux react-redux redux-thunk redux-logger history@4.10.1 connected-react-router@6.8.0 immer redux-actions

npm i redux react-redux redux-thunk redux-logger history@4.10.1 connected-react-router@6.8.0 
immer redux-actions

 

각 모듈에 대한 설명

 

redux-logger : 웹상에서 redux의 action이 일어날 때마다 콘솔에서 action 내역을 이쁘게 보여주는 모듈

 

 

 

 

 

이런 식으로 action이 일어날 때마다 action 이전 state 값, action , action 이후 state 값 

한 프로젝트 안에 action을 많이 다룬다면 logger 사용을 강력 추천한다. 

 

 

history@4.10.1 , connected-react-router@6.8.0 : redux에서 history 객체를 사용할 수 있게 해주는 모듈

immer : reducer 불변성 관리하게 해주는 모듈

redux-action : redux action 구문을 더욱 편리하게 작성할 수 있게 해주는 모듈

 

 

초기 셋팅 방법

차례 : 리덕스 스토어 만들기 -> 리덕스 주입하기 -> 스토어 및 라우터 적용하기 -> action 및 router 적용하기

 

워낙 편리하게 해주는 모듈이다 보니 초기 세팅 난이도가 거의 이클립스 설치 및 환경변수 급의 난이도를 자랑한다.

 

 

 

 

프로젝트 폴더 구조
ㄴ public
ㄴ node_modules
ㄴ src
     ㄴ index.js
     ㄴ App.js
     ㄴ redux
           ㄴ configureStore.js
           ㄴ module
                  ㄴ user.js

 

 

 

1. 리덕스 스토어 만들기 (configureStore.js)

 

 

import { createStore, combineReducers, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import { createBrowserHistory } from "history";
import { connectRouter } from "connected-react-router";

import User from "./modules/user";		// user Action Module
export const history = createBrowserHistory();

// rootReducer 만들기
const rootReducer = combineReducers({
    user: User,		// 여기에 리듀서가 들어있는 모듈들을 넣어준다.
    router: connectRouter(history),
});

//미들웨어 준비
const middlewares = [thunk.withExtraArgument({history:history})];        // 사용하고 싶은 미들웨어를 이 배열안에 넣어줘야한다.  //withExtraArgument에다 history를 넣어주면 reducer로 가기전에 history를 사용할 수 있다.

// 지금이 어느 환경인 지 알려준다. (개발환경, 프로덕션(배포)환경 ...) 내가 어느 환경에 있는지 알려주는 것이다.
const env = process.env.NODE_ENV;

// redux-logger를 사용하기 위한 작업
if (env === "development") {
  const { logger } = require("redux-logger");       // 왜 굳이 import 가 아닌 require를 썻는가? 이 모듈은 dev환경에서만 쓰이고 build(배포)환경에선 쓰이지 않기 때문이다. 굳이 import해서 프로그램 용량을 차지하면 낭비이다.
  middlewares.push(logger);
}
// redux devTools 설정
const composeEnhancers =
  typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__         // 지금 돌아가는 환경이 브라우져가 아니면 window라는 객체가 없다. 브라우져일때만 실행되게 한후
    ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({                                 // 리덕스 데브툴즈를 실행한다.
        // Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
    })
: compose;
// 미들웨어 묶기
const enhancer = composeEnhancers(          // 우리에게 사용하는 미들웨어를 묶어준다.
    applyMiddleware(...middlewares)
);
// 스토어 만들기
let store = (initialStore) => createStore(rootReducer, enhancer);

export default store();

 

 

한 번에 이해하기 어려울 것이다.

사실 지금 나도 이 코드를 안보고 쓰라하면 절대 못쓴다...

코드를 외울려 하지 말고 코드 한줄한줄이 무엇을 의미하는지를 보자

 

2. 리덕스 주입하기 (index.js)

 

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';

import './index.css';
import App from './shared/App'

import store from './redux/configureStore'
ReactDOM.render(
  <Provider store={store}>			// 스토어 주입하기!!
    <App />
  </Provider>,
  document.getElementById('root')
);

 

 

3. 스토어 및 라우터 적용하기(App.js)

 

 

import './App.css';
import React from "react";

import {Route} from "react-router-dom";
import {ConnectedRouter} from 'connected-react-router'
import {history} from '../redux/configureStore'

import PostList from "../pages/PostList";
import Login from "../pages/Login";
import Signup from "../pages/Signup";


function App() {
  return (
    <React.Fragment>
        <ConnectedRouter history={history}>			// connectedRouter의 history 주입하기!!
          <Route path="/" exact component={PostList} />
          <Route path="/login" exact component={Login} />
          <Route path="/signup" exact component={Signup}/>
        </ConnectedRouter>
    </React.Fragment>
  );
}

export default App;

 

 

4. action 및 reducer 만들기(user.js)

 

 

import { createAction, handleActions } from "redux-actions";
import { produce } from "immer";

// actions
const LOG_IN = "LOG_IN";
const LOG_OUT = "LOG_OUT";

// action creators
const logIn = createAction(LOG_IN, (user) => ({ user }));		// 여기에 들어간 인자는 action.payload에 들어가게 된다.
const logOut = createAction(LOG_OUT, (user) => ({ user }));

// initialState
const initialState = {
	user: null,
    is_login: false,
}

// reducer
export default handleActions({ 
	[LOG_IN] : (state, action) => produce(state, (draft) => { 		// 액션 명을 기술한다.
		draft.user = action.payload.user;		// draft는 state을 접근할 수 있는 객체(?)이며, 아까 createAction 넣었던 인자를 action.payload로 접근할 수 있다.
		draft.is_login = true;
	}), 
	[LOG_OUT] : (state, action) => produce(state, (draft) => { 
		draft.user = null;
		draft.is_login = false;
	}), 
}, initialState );		// reducer 대신 handleActions을 쓰고 1번째 인자는 reducer내용을 작성하고 2번째 인자는 초기 state 값을 넣는다.

// action creator export
const actionCreators = {
	logIn,
    logOut,
};

export { actionCreators };

 

 

redux-action은 이런 식으로 만든다.

 

그럼 redux-action과

본래의 redux와 차이점을 비교해보자

 

 

// actions
const LOG_IN = "LOG_IN";
const LOG_OUT = "LOG_OUT";

// action creators
const logIn = (user) => {
	return { type: LOG_IN, user }
}
const logOut = (user) => {
	return { type: LOG_OUT, user }
}
// initialState
const initialState = {
	user: null,
    is_login: false,
}

// reducer
export default function reducer(state = initialState, action = {}){ 
	switch(action.type){
    	case "LOG_IN" : {
        	return {...state, user: action.user};
        }
        case "LOG_OUT" : {
        	return {...state, user: null, is_login : false};
        }
    default: return state;
	}
}


// action creator export
const actionCreators = {
	logIn,
    logOut,
};

export { actionCreators };

 

 

다른 것들은 거의 흡사하고 action creator와 reducer이 두 가지가 다르다.

 

다만 immer를 쓰는 이유는

객체는 const로 선언해도 내용이 수정될 수 있다. 그래서 스프레드 문법(...state) 등을 이용해서 수정되지 않게 사용하는 것이 redux의 국룰입니다.

그런데 객체 구조가 복잡해지면 코드를 짜기가 번거로워진다. 그래서 불변성을 신경 쓰지 않는 것처럼 써도 알아서 불변성을 유지해주는 것이 immer이다.

 

이러한 점을 고려하여 원래 redux와 redux-action을 잘 선택해서 사용하길 바란다.