코딩애플님의 '빠르게 마스터하는 타입스크립트' 강의를 수강한 뒤 정리를 위해 작성한 글입니다.
Redux-tookit은 보다 간단한 문법으로 리덕스를 사용해 줄 수 있게 해 주는 (무려) 공식 라이브러리입니다. 🥳
npm install @reduxjs/toolkit react-redux // or yarn add @reduxjs/toolkit react-redux
// src/redux/UI/store.ts
import UIStoreType from './type';
const UIStore: UIStoreType = {
modal: false,
};
export default UIStore;
state의 초기값을 정의해 주는 store.ts 파일을 만들어 줍니다.
간단하게 모달을 열고 닫을 용도로 쓸 거라서 객체 안에 modal이라는 속성을 만들어주었습니다.
// src/redux/UI/type.ts
import { ReactNode } from 'react';
interface UIStoreType {
modal: boolean | ReactNode;
}
export default UIStoreType;
만들어 준 store 객체의 타입을 정의해주기 위해, 같은 폴더에 type.ts를 만들어줍니다.
상태가false
값이면 모달이 보이지 않고, false
값이 아니면 state로 지정해 준 컨텐츠가 표시되는 모달을 만들기 위해 boolean | ReactNode
로 타입을 지정해주었습니다.
// src/redux/UI/action.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { ReactNode } from 'react';
import UIStore from './store';
const UISlice = createSlice({
name: 'UI',
initialState: UIStore,
reducers: {
setModalContent(state, action: PayloadAction<ReactNode>) { // 모달 컨텐츠 지정 및 모달 열기
state.modal = action.payload;
},
setModalClose(state) { // 모달 닫기
state.modal = false;
},
},
});
export const { setModalClose, setModalContent } = UISlice.actions;
export default UISlice;
action.ts 파일을 만들고 상태를 조작하는 방법을 정의합니다.
리덕스 툴킷은 createSlice
함수로 슬라이스를 만들어 그 안에서 리듀서를 정의해 줄 수 있는 특징이 있습니다🤔
슬라이스를 생성하려면
name
속성)reducers
)원래 Redux는 데이터 복사본을 만들고 복사본을 업데이트해서 상태를 불변적으로 업데이트해야만 하는데, redux-toolkit의 createSlice
API를 사용하면 Mobx처럼 편하게 상태를 업데이트 할 수 있다고 합니다. 👩🚀
reduxer의 첫 파라미터에는 state가, 두 번째 파라미터에는 action이 자동으로 부여됩니다.
reducers: {
setModalContent(state, action: PayloadAction<ReactNode>) {
state.modal = action.payload;
},
...
action에 payload(dispatch 시 같이 보낼 데이터) 가 있으면 자료의 타입을 PayloadAction
의 제네릭에 넣어서 타입을 지정해 주면 된다고 합니다.
// src/redux/index.ts
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
import UISlice, { setModalClose, setModalContent } from './UI/action';
import ContentSlice, { setTodayFeel, setTodayFeelDesc, setReset } from './Content/action';
const indexStore = configureStore({
reducer: {
ui: UISlice.reducer,
content: ContentSlice.reducer,
},
middleware: getDefaultMiddleware({
serializableCheck: false,
}),
});
// 리듀서들을 하나의 객체로 묶어서 내보내기
export const indexAction = { setModalClose, setModalContent, setTodayFeel, setTodayFeelDesc, setReset };
// state 타입을 export
export type RootStateType = ReturnType<typeof indexStore.getState>;
export default indexStore;
만들어 준 리듀서는 configureStore
라는 함수를 import해와서 등록해 줍니다.
저는 상태로 컴포넌트를 넘겨 주었기 때문에 콘솔창에서 직렬화 관련 경고가 계속 발생해서 추가로 serializableCheck: false
설정을 해 주었는데, store의 일관성 유지, 미들웨어 사용시 오류 발생 가능성 높음, 디버깅 오류 등이 발생할 수 있기에 권장되는 설정은 아니라고 합니다 😭 (https://ymc-crow.github.io/basic/redux%EC%99%80-serializable/)
//_app.tsx
//...
import ModalView from '@/components/common/modal/ModalView';
import { Provider } from 'react-redux';
import indexStore from '@/redux';
//...
function App({ Component, pageProps }: AppProps) {
return (
<>
//...
<Provider store={indexStore}>
<Component {...pageProps} />
<ModalView />
</Provider>
</>
);
}
export default App;
최상위 컴포넌트에(next.js의 경우 _app.tsx) redux의 Provider
컴포넌트를 불러오고,
configureStore
로 등록해 준 리듀서와 스토어 파일을 가져와서 store
속성으로 설정해줍니다.
// ModalView.tsx
import { RootStateType } from '@/redux';
import { useSelector, useDispatch } from 'react-redux';
import { indexAction } from '@/redux';
import Modal from './ModalViewStyle';
const ModalView: React.FC = () => {
const rootState = useSelector((state: RootStateType) => state);
const dispatch = useDispatch();
/** FUNCTION 기록하기 버튼 클릭 시 실행 */
const setModalClose = (e: React.MouseEvent<HTMLButtonElement>) => {
if (e.target !== e.currentTarget) return;
dispatch(indexAction.setModalClose());
};
return (
<>
{rootState.ui.modal !== false && (
<Modal.Section className='Modal' onClick={setModalClose}>
<Modal.Container className='Modal__container'>{rootState.ui.modal}</Modal.Container>
</Modal.Section>
)}
</>
);
};
export default ModalView;
import { RootStateType } from '@/redux';
import { useSelector } from 'react-redux';
리덕스 state를 사용할 곳에 redux에서 제공하는useSelector
hook과 configureStore
로 리듀서를 등록하면서 export 해 줬던 state 타입을 불러옵니다.
...
const ModalView: React.FC = () => {
const rootState = useSelector((state: RootStateType) => state);
...
useSelector
안 콜백함수의 첫 파라미터는 항상 state가 됩니다. state에 타입 지정을 하고 그대로 반환해 주면 configureStore
에서 등록해 줬던 store state에 접근할 수 있습니다.
return (
<>
{rootState.ui.modal !== false && (
<Modal.Section className='Modal' onClick={setModalClose}>
<Modal.Container className='Modal__container'>{rootState.ui.modal}</Modal.Container>
</Modal.Section>
)}
</>
);
configureStore
에서 정의해 줬던 이름대로 접근이 가능합니다.
import { useDispatch } from 'react-redux';
import { indexAction } from '@/redux';
configureStore
정의할 때 묶어서 export해준 action과, redux에서 제공하는 useDispatch
함수를 가져와 줍니다.
const ModalView: React.FC = () => {
const dispatch = useDispatch();
...
// 모달 닫아 주는 함수
const setModalClose = (e: React.MouseEvent<HTMLButtonElement>) => {
if (e.target !== e.currentTarget) return;
dispatch(indexAction.setModalClose());
};
...
<Modal.Section className='Modal' onClick={setModalClose}>
...
dispatch(indexAction.setModalClose());
dispatch
함수 파라미터로 실행하고 싶은 action을 넣어 주면 끝! 👻
확실히 toolkit 라이브러리 없이 redux를 사용하는 것보다 한결 사용하기 쉬워진 느낌이긴 한데, Mobx를 주로 사용하다 보니 이것저것 너무 설정해주어야 할 게 많아서 번잡스럽다는 느낌은 들긴 합니다...🤖 (Mobx 짱!)