React에서 보통 상태관리 툴로 Redux를 사용하는데 Redux에는 액션함수, 리듀서, 스토어 등으로 작성하는 코드가 많고 가독성이 좋지 않다.
그래서 Redux-Toolkit을 공부하며 프로젝트에 도입해보려한다!
다음과 같이 설치해준다.
npm install @recuxjs/toolkit react-redux
만약 프로젝트에 버전 7.2.3 이하의 버전이라면 다음 코드를 추가한다.
npm install @types/react-redux
Slice란 기능별로 store를 나누는 것이다.
예를 들어 쇼핑몰 프로젝트에서 진행한다면 1)회원가입, 2)로그인, 3)장바구니 등의 기능으로 나루 수 있다. 따라서 state를 이런 기능으로 나누는 것이다.
Slice 파일들은 보통 src/features 폴더에 저장한다.
Slice를 만들 때 사용하는 함수가 createSlice()이다.
slice의 state의 타입을 우선 정의해야한다. interface로 타입을 정한 후에 initialState의 값에 초기값을 정한다.
import { createSlice } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'
interface CounterState {
value: number
}
const initialState : CounterState = { value: 0 }
아래 코드는 간단하게 카운트를 증감하는 예제이다.
여기서 createSlice 함수는 파라미터에 1)name, 2)initialState, 2) reducers 로 총 3개를 작성하면 된다.
1) name : createSlice 함수를 통해서 위에서 말한 것과 같은 기능마다 state를 나누는 slice를 생성할 수 있다. name은 이런 slice의 중복을 피하기 위해 정하는 고육한 이름 값이다.
2) initialState : 상태관리에 사용되는 type이다. interface로 타입을 지정하고 initialState의 값을 초기화하는데 카운터이기에 value를 0으로 설정했다.
3) reducers : 액션타입에 따라 상태변화를 처리하는 함수를 정의 한다. 함수의 이름은 dispatch로 부르는 액션함수의 이름이고 함수 내부에서는 state의 상태를 변경한다. 그리고 reducer에서 payload를 인자로 받는 경우 아래와 같이 action.payload의 타입을 정의해준다.
src/features/counterSlice.ts
import { createSlice } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'
interface CounterState {
value: number
}
const initialState : CounterState = { value: 0 }
const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: state => {
state.value += 1
},
decrement: state => {
state.value -= 1
},
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload
}
})
이러한 기능별로 나눈 slice 안의 reducer를 configureStore에 등록시킨다.
import { configureStore } from '@reduxjs/toolkit';
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});
컴포넌트에서 action을 사용한다면 'react-redux'의 useDispatch를 사용해서 action을 dispatch하면 그에 따른 reducer가 실행된다.
import { useDispatch } from 'react-redux';
import { increment } from '../features/counter/counterSlice'
function App() {
const dispatch = useDispatch();
// ...
return (
<div>
<button onClick={() => dispatch(increment())}>increment</button>
</div>
)
컴포넌트에서 store에 저장된 state에 접근하고 싶다면 'react-redux'의 useSelector을 사용해주면 된다. useSelector에 store자체를 넘기고 이 store를 통해 slice의 state에 접근할 수 있다.
import { useSelector } from 'react-redux';
function App() {
const { count } = useSelector((store) => store.counter);
// ...
}
타입스크립트를 사용하여 컴포넌트에서 useSelector와 useDispatch를 사용하기 위해서는 타입스크립트 버전으로 바꿔줘야 한다.
이런 타입화 과정을 매번 컴포넌트에서 하면 비효율적이기 때문에 hooks.ts라는 파일안에 pre-typed 버전을 만들어주었다.
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './store'
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
위와 같이 hooks.ts 파일을 만든 후 컴포넌트에서 dispatch와 selector을 사용할 수 있다.
import { useAppSelector, useAppDispatch } from '../hooks'
import { decrement, increment } from './store/counterSlice'
export function Counter() {
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>
)
}
위에서 작성한 store를 전역에 사용가능하도록 App의 시작점인 index.tsx파일에 Provider로 등록해준다.
import {Provider} from "react-redux"
import {store} from './store/store'
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
다음에는 Redux-Toolkit을 활용하여 비동기 처리를 해보려고 한다!