Redux에 대해 정리하면서 추가적으로 알아야하는 개념이나 통상적으로 만드는 디자인 형태에 대해 정리할 필요성을 느꼈다. Action creator 함수를 선언해야하는 필요성, 리듀서를 분리해서 구현하는 이유, persistReducer
추가 모듈을 사용해야하는 이유 등 알아야할 개념들이 많다. 이 글에서 정리해본다. react-saga, react-thunk 이런건 다음 포스팅에서 보자. 졸립다2.
쉽게 말해 액션 객체를 반환하는 순수함수다. 불필요한 작업을 극혐하는 사람으로써 굳이 이렇게 함수를 선언해서 관리포인트를 늘려야하는 이유에 대해 궁금해졌다. 의외로 답은 간단하다. 인간은 다른 인간을 못믿기 때문이다.
// 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;
}
}
dispatch({
type : 'EDIT_NAME',
payload : { name : 'durrian' }
});
const actionCreator = (state) => ({
type: 'EDIT_NAME',
payload : { name : state.name }
});
dispatch(actionCreator({ name : 'durian' }));
결론적으로는 같은 코드고 같은 결과를 수행한다. Action creator를 사용하는 이유는 크게 2가지 이유 정도로 정리할 수 있다.
관리포인트와 가독성, 즉 유지보수 측면에서 용이하게 쓸수 있도록 구성 가능하다.
이건 뭔고 하니, 위 예제에서 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;
일단 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인데 새로고침하면 데이터가 초기화되는 아이러니가 있다. 이를 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