토이 프로젝트를 위해 지난번 React(TypeScript) APP을 만들었으나, 상태(state) 관리 라이브러리를 사용하기 위해 Redux를 구축하려고 한다.
크게 특별할 것이 없다. 참고로 @types/react-redux는 아래 패키지에 dependency로 포함된다고 한다. 그리고 참고로 CRA를 이용하면 초기 APP을 만들 때 redux도 함께 설치할 수 있다. 저자는 모르고 넘어와 따로 구축하고 있다. (...하하)
npm install react-redux @reduxjs/toolkit
@reduxjs/toolkit 패키지는 redux에서 사용하기 쉽게 배포하는 toolkit이라고 한다. 사용하지 않으면 다른 라이브러리를 많이 추가하거나, 코드가 길어지고 러닝커브가 높다고 한다.
// src/redux/store.ts
import { configureStore } from '@reduxjs/toolkit'
export const store = configureStore({
reducer: {
// 여기에 reducer를 추가하는 방식이다.
},
});
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch;
// src/redux/hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './store'
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./pages/App";
import { Provider } from "react-redux";
import { store } from './redux/store'
ReactDOM.render(
<Provider store={store}> // 이게 추가된 것이다!
<App />
</Provider>,
document.getElementById("root")
);
// src/components/molecules/Counter.tsx
import React, { useState } from 'react'
import { useAppSelector, useAppDispatch } from '../../redux/hooks'
import { decrement, increment } from '../../redux/reducers/counterSlice'
export function Counter() {
// The `state` arg is correctly typed as `RootState` already
// hook을 정의 안하면 이렇게 state에 RootState type을 줘야하고 import를 해야만 한다.
// const count = useSelector((state: RootState) => state.counter.value)
const count = useAppSelector((state) => state.counter.value);
const dispatch = useAppDispatch();
return (
<div>
<div>
<button
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
Increment
</button>
<span>{count}</span>
<button
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
Decrement
</button>
</div>
</div>
);
}
// src/redux/reducers/counterSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import type { RootState } from '../store'
// Define a type for the slice state
interface CounterState {
value: number
}
// Define the initial state using that type
const initialState: CounterState = {
value: 0,
}
export const counterSlice = createSlice({
name: 'counter',
// `createSlice` will infer the state type from the `initialState` argument
initialState,
reducers: {
increment: (state) => {
state.value += 1
},
decrement: (state) => {
state.value -= 1
},
// Use the PayloadAction type to declare the contents of `action.payload`
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
// Other code such as selectors can use the imported `RootState` type
export const selectCount = (state: RootState) => state.counter.value;
export default counterSlice.reducer;
// src/redux/store.ts
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './reducers/counterSlice'
export const store = configureStore({
reducer: {
counter: counterReducer, // 이렇게 추가해준다!
},
});
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch;
// src/pages/App.tsx
import React from "react";
import { Counter } from "../components/molecules/Counter";
const App = () => {
return (
<>
<h3>bevelog</h3>
<Counter />
</>
);
};
export default App;
최종 결과 폴더 구조는 다음과 같다.