출처
How to use Redux in Next.js
Redux - Useage with TypeScript
폴더 구조는 아래와 같이 구성된다.
/store
/slices
slice.ts
hooks.ts
index.ts
리덕스 툴킷과 리액트와 리덕스를 연결해주는 패키지를 설치해준다.
npm install @reduxjs/toolkit react-redux
또한 리덕스 툴킷과 Next.js를 연동할 패키지도 설치해준다.
npm install next-redux-wrapper
각 목적에 따라 나뉘어진 슬라이스 리듀서 함수를 만든다.
interface InitialLangState {
lang: string,
}
const initialState: InitialLangState = {
lang: "en"
}
import { createSlice, PayloadAction } from "@reduxjs/toolkit"
import { HYDRATE } from "next-redux-wrapper"
우선 Redux Toolkit 패키지를 통해 슬라이스를 생성할 떄 사용되는 createSlice 메서드를, 리듀서 함수 내부 action의 payload 프로퍼티의 타입을 설정할 때 사용하는 PayloadAction을 불러온다.
이후 서버와 클라이언트의 상태 일치를 위한 HYDRATE도 next-redux-wrapper 패키지에서 불러온다.
const langSlice = createSlice({
name: "lang",
initialState,
reducers: {
changeCurrentLang: (state: InitialLangState, action: PayloadAction<string>) => {
state.lang = action.payload
}
},
extraReducers: {
[HYDRATE]: (state, action) => {
console.log('HYDRATE', state, action.payload)
return {
...state,
...action.payload.lang,
}
}
}
})
이후 슬라이스를 생성하고 객체 형태로 리듀서 함수를 넣어준다.
해당 슬라이스의 이름
해당 슬라이스의 초기 상태
초기 상태를 제어할 리듀서 함수들, 액션 생성자 함수는 reducers 내부 프로퍼티 명으로 대체한다.
말 그대로 슬라이스 외부에서 정의한 액션에 대한 처리 로직을 적어준다. 위 예시에서는 HYDRATE 단계일 때를 명시해준다.
export const { changeCurrentLang } = langSlice.actions
export const langReducer = langSlice.reducer
추후 컴포넌트에서 액션 생성자 함수를 디스패치할 수 있게 reducers 내부에서 선언한 액션 생성자 함수와 리듀서를 내보낸다.
import { configureStore } from '@reduxjs/toolkit'
import { langReducer } from './slices/langSlice'
export const makeStore = configureStore({
reducer: {
lang: langReducer
},
devTools: true
})
우선 기존에 만든 슬라이스에서 리듀서 함수를 불러오고 configureStore 에 들어갈 객체 형태의 인수에서 reducer 프로퍼티에 해당 리듀서를 넣어준다.
이후 스토어, 상태에 대한 타입과 useDispatch, useSelector 훅에 적용할 타입을 따로 선언한다.
import { configureStore, ThunkAction } from '@reduxjs/toolkit'
import { Action } from 'redux'
import { createWrapper } from 'next-redux-wrapper'
// reducers
import { langReducer } from './slices/langSlice'
// Root reducer를 사용하는 대신, 각 slice의 reducer를 할당한다.
const makeStore = () => configureStore({
reducer: {
lang: langReducer
},
devTools: true,
})
export type AppStore = ReturnType<typeof makeStore>
export type AppState = ReturnType<AppStore['getState']>
export type AppDispatch = AppStore['dispatch']
export type AppThunk<ReturnType = void> = ThunkAction<ReturnType, AppState, unknown, Action>
export const wrapper = createWrapper<AppStore>(makeStore)
이후 createWrapper 메서드를 통해 Redux-toolkit과 Next.js를 연결해준다.
import { useDispatch, useSelector } from "react-redux"
import type { TypedUseSelectorHook } from 'react-redux'
import type { AppState, AppDispatch } from "."
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<AppState> = useSelector
이후 컴포넌트에서 useSelector, useDispatch 훅을 사용할 경우 미리 타입이 선언이 된 useAppDispatch, useAppSelector를 불러와서 사용하도록 한다.
import { useAppDispatch, useAppSelector } from "../store/hooks"
import { changeCurrentLang } from "../store/slices/langSlice"
export default function HomePage() {
const lang = useAppSelector((state) => state.lang.lang)
const dispatch = useAppDispatch()
...
}