[ React.07.상태관리 - Redux ETC ]

carrotsman·2023년 2월 27일
0

프론트엔드

목록 보기
29/34
post-thumbnail

Redux 짜바리들

Redux에 대해 정리하면서 추가적으로 알아야하는 개념이나 통상적으로 만드는 디자인 형태에 대해 정리할 필요성을 느꼈다. Action creator 함수를 선언해야하는 필요성, 리듀서를 분리해서 구현하는 이유, persistReducer 추가 모듈을 사용해야하는 이유 등 알아야할 개념들이 많다. 이 글에서 정리해본다. react-saga, react-thunk 이런건 다음 포스팅에서 보자. 졸립다2.


Action creator

쉽게 말해 액션 객체를 반환하는 순수함수다. 불필요한 작업을 극혐하는 사람으로써 굳이 이렇게 함수를 선언해서 관리포인트를 늘려야하는 이유에 대해 궁금해졌다. 의외로 답은 간단하다. 인간은 다른 인간을 못믿기 때문이다.

  • 리듀서 정보
// reducer
const reducer = (state = initialState, action) => {
  if (action.type == 'EDIT_NAME') {
    return { ...state, name : action.payload.name };
  } else if (action.type == 'EDIT_AGE') {
    return { ...state, age : action.payload.age };
  } else {
    return state;
  }
}
  1. 일단 기본적으로 액션 객체를 전달하는 방법이다.
dispatch({ 
  type : 'EDIT_NAME', 
  payload : { name : 'durrian' } 
});
  1. Action creator 함수를 사용하는 경우는 다음과 같다.
const actionCreator = (state) => ({
  type: 'EDIT_NAME',
  payload : { name : state.name }
});

dispatch(actionCreator({ name : 'durian' }));

결론적으로는 같은 코드고 같은 결과를 수행한다. Action creator를 사용하는 이유는 크게 2가지 이유 정도로 정리할 수 있다.

  1. dispatch 실행시 항상 새로운 객체를 선언해줘야한다. 반면 Action creator를 사용하게 되면 생성되는 객체의 관리를 일원화할 수 있고, 실수를 줄일 수 있다.
  2. Action creator를 하나 또는 기능 단위 묶음으로 구성해놓으면 새로운 개발자 참여시 해당 페이지만 참조하여 사용법을 익힐 수 있다. API 문서와 같은 역할을 한달까

관리포인트와 가독성, 즉 유지보수 측면에서 용이하게 쓸수 있도록 구성 가능하다.


bindActionCreators

이건 뭔고 하니, 위 예제에서 dispatch(actionCreator({ name : 'durian' })); 이것도 귀찮다 이거다. Action creator와 dispatch함수를 결합한 모듈을 반환해 사용하는 방법이다. 간단한 예시만 보고 넘어가자 11시다.

👽 구조는 구성하기 나름이다. 사실상 구데기 구조인데 이해를 위해 대충 만들었다.

ActionCreator.js

export const setName = (state) => ({
  type: EDIT_NAME, payload : { name : state.name }
});

export const setAge = (state) => ({
  type: EDIT_AGE, payload : { age : state.age }
});

index.js

// 쓸데없는건 지웠다.
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux'; // Provider
import App from './App';
import store from './store/store'; // 위에서 만든 store

import { bindActionCreators } from 'redux';
import * as ActionCreator from './store/lib/actionCreator';

const root = ReactDOM.createRoot(document.getElementById('root'));
const { dispatch } = store;

// actionCreator의 액션 함수와 store 내부 dispatch 함수를 인자로 bindActionCreators 생성
const actionCreator = bindActionCreators(ActionCreator, dispatch);

root.render(
  <Provider store={store}> 
    <App actionCreator={actionCreator}/>
  </Provider>
);

App.js

import { useSelector } from 'react-redux';

const App = (props) => {
  const userInfo = useSelector((state) => state);
  const actionCreator = props.actionCreator; // props로 넘겨받은 actionCreator객체 사용

  return (
    <div className="App">
      <button onClick={() => { 
          actionCreator.setName({ name : 'durian' });
        }}>개명</button>
      <p>안녕? 나는 {userInfo.name}! 내 나이는 {userInfo.age}</p>
    </div>
  );
}

export default App;

combineReducers

일단 Redux는 1개의 스토어에 1개의 리듀서를 기본 구조로 삼는다. 하지만 관리포인트에 따라 전역 state에 대한 변경 함수를 분리, 관리하고 싶어진다. 그때 사용할 수 있는 것이 combineReducers다. 간단하게 여러 개의 리듀서를 1개의 리듀서로 병합한다고 생각하면 쉽다.

store.js

import { createStore, combineReducers, applyMiddleware } from 'redux';
import loggerMiddleware from './lib/loggerMiddleware';

// 관심사별 State 분리
const initialState = {
  userInfo : {
    name : 'carrots',
    age : 25
  },
  siteInfo : {
    key : ''
  }
};

// AUTH reducer 선언
const authReducer = (state = initialState, action) => {
  if (action.type == 'EDIT_NAME') {
    return { ...state, userInfo : { ...state.userInfo, name : action.payload.name } };
  } else if (action.type == 'EDIT_AGE') {
    return { ...state, userInfo : { ...state.userInfo, age : action.payload.age }  };
  } else {
    return state;
  }
}

// SITE reducer 선언
const siteReducer = (state = initialState, action) => {
  if (action.type == 'SET_SITE_KEY') {
    return { ...state, siteInfo : { ...state.siteInfo, key : action.payload.key } };
  } else {
    return state;
  }
}

// AUTH reducer, SITE reducer를 1개의 리듀서로 병합
const rootReducer = combineReducers({
  auth: authReducer,
  site: siteReducer
});

const store = createStore(rootReducer, applyMiddleware(loggerMiddleware));
export default store; 

redux-persist

분명 전역적으로 사용되는 상태관리에 사용되는 것이 Redux인데 새로고침하면 데이터가 초기화되는 아이러니가 있다. 이를 LocalStorage, SessionStorage를 활용하여 상태를 저장하게 할 수 있는 redux-persist로 해결한다. 얘도 이해의 영역이 아니다. 그냥 이렇게 쓰라

redux-persist 패키지 설치

npm install redux-persist

store.js

import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage'; // localStorage

// ...

// AUTH reducer, SITE reducer를 1개의 리듀서로 병합
const rootReducer = combineReducers({
  auth: authReducer,
  site: siteReducer
});

const persistConfig = {
  key: 'redux',
  storage,
  whitelist: ['auth'], // 리듀서 명칭이 들어가고 해당 리듀서만 storage에 저장됨
  blacklist: ['site']  // 반대로 저장되지 않아야되는 리듀서 지정
};

const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = createStore(persistedReducer,  applyMiddleware(loggerMiddleware));
export const persistor = persistStore(store); 
export default store; 

index.js

import { PersistGate } from 'redux-persist/integration/react';

// ...

root.render(
  <Provider store={store}> 
    <PersistGate loading={null} persistor={persistor}>
      <App actionCreator={actionCreator}/>
    </PersistGate>
  </Provider>
);

실행하고 뭐시기하고 디버거로 LocalStorage를 확인해보면

새로고침해도 유지되는 것을 확인할 수 있다.


정리하며

오늘은 기타 Redux 추가 기능에 대해 알아봤다. useSelector 최적화와 connect 함수도 다뤄보려했는데 너무 졸려서 이만 줄인다.

오늘 저녁은 롯데리아다.

참고 : https://velog.io/@dom_hxrdy/Redux-action-creator%EC%9D%98-%EC%A1%B4%EC%9E%AC-%EC%9D%B4%EC%9C%A0%EC%97%90-%EB%8C%80%ED%95%9C-%EC%82%AC%EC%83%89
https://ko.redux.js.org/api/bindactioncreators/
https://github.com/rt2zz/redux-persist/blob/master/docs/api.md

profile
당근먹고 강력한 개발

0개의 댓글