๋ฆฌ๋์ค ํดํท์ ๋ฆฌ๋์ค์ ๋ณต์กํ ํ๊ฒฝ์ค์ ์ ๊ฐ๋จํ๊ฒ ํด์ค๋๋ค. ์์ธํ ๊ฑด ๊ณต์๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ๋ฉด ์ข์ต๋๋ค.
๋ช ๊ฐ์ง ๊ฐ๋ ๋ง ์์๋๋ฉด ํธํ๊ฒ ์ฌ์ฉํ ์ ์์ต๋๋ค.
action, ruducers ๋ฑ๋ฑ๋ฑ ๊ด๋ จ๋ ๊ฑธ ํธํ๊ฒ ํ๋ฒ์ ์ ์ํ๋ ๊ฒ์ ๋งํฉ๋๋ค. ์ด๋ฆ์ ๊ทธ๋ฅ slice๋ผ๊ณ ๊ณต์๋ฌธ์์์ ๋ถ๋ฆ ๋๋ค. ์ด์ ๋ ๋ชจ๋ฆ
์์(๊ตฌ์ฑ)
import { createSlice } from '@reduxjs/toolkit';
// ํ์
์ ์ธ
// ์ด๊ธฐ ์ํ ์ ์
const initialState = { value: 0 };
// createSlice ์๋ฆฌ
// action & reducers ๋์์ ํธํ๊ฒ ์ค์ ํ ์ ์๊ฒํฉ๋๋ค.
// name, ์ด๊ธฐ๊ฐ, reducers๋ง ๋ฃ์ด์ฃผ๋ฉด ๋ฉ๋๋ค.
const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
},
});
// ์ก์
์์ฑ ํจ์๋ ๋ฆฌ๋์๋ฅผ ๋ฐ๊นฅ์ผ๋ก ๋นผ์ค์ผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
export const { increment, decrement } = counterSlice.actions; // ์ก์
์์ฑํจ์
export default counterSlice.reducer; // ๋ฆฌ๋์
์ด๋ ๊ฒ ์ ์ํ ์ก์ ๋ค๊ณผ ๋ฏธ๋ค์จ์ด ๋ฑ๋ฑ์ ์ ์ํ๋ ๊ณณ์ ๋๋ค. ๋ง์ฝ context api์ redux ์ค ๊ณ ๋ฏผํ๊ณ ์๋ค๋ฉด, ๋จ์ง reducer์ action, dispatch๋ง ์ฌ์ฉํ๋ ค๋ฉด ์ ์๋ฅผ, ๋ฏธ๋ค์จ์ด๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ๋ฆฌ๋์ค๋ฅผ ์ ํํ๋ ๊ฒ ๋ง๋ค๋ ์ด์ผ๊ธฐ๋ฅผ ์ด๋์ ๋ณด์์ต๋๋ค... ๐โโ๏ธ
๋ณดํต ์๋์ ๊ฐ์ด ์๊ฒผ์ต๋๋ค.
const store = configureStore({
reducer: ์ฌ์ฉํ๋ ๋ฆฌ๋์๋ฅผ ๊ฐ์ ธ์ต๋๋ค.,
middleware: ๋ฏธ๋ค์จ์ด ์ ์,
devTools: process.env.NODE_ENV !== 'production',
})
๋ฏธ๋ค์จ์ด๋ฅผ ์ฌ์ฉ๊น์ง ํฌํจ๋ ์์
import { configureStore } from '@reduxjs/toolkit'
import additionalMiddleware from 'additional-middleware'
import logger from 'redux-logger'
// @ts-ignore
import untypedMiddleware from 'untyped-middleware'
import rootReducer from './rootReducer'
export type RootState = ReturnType<typeof rootReducer>
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware()
.prepend(
// correctly typed middlewares can just be used
additionalMiddleware,
// you can also type middlewares manually
untypedMiddleware as Middleware<
(action: Action<'specialAction'>) => number,
RootState
>
)
// prepend and concat calls can be chained
.concat(logger),
devTools: process.env.NODE_ENV !== 'production',
})
export type AppDispatch = typeof store.dispatch
export default store
logger
action๊ณผ Payload๋ฅผ ๋ธ๋ผ์ฐ์ ์ฝ์์ ์๋์ผ๋ก ์ฐ์ด์ค๋๋ค. ๊ฐ๋ฐ๋ชจ๋์์๋ง ์คํ๋๋๋ก ์ค์ ์ ํด๋ฌ์ผํ๋๋ฐ ๊ทธ๊ฑฐ ๋ฐ๋ก devTools: process.env.NODE_ENV !== 'production'
๋ถ๋ถ ์
๋๋ค.
์ด ์ธ์๋ ๋ง์๋ฐ ์ข์ ๊ฑธ ์ฐพ์์ ์ฐ๋ฉด ๋ฉ๋๋ค!
import { useDispatch } from 'react-redux';
import { ์คํ ์ด์์ ๋ด๋ณด๋ธ ๋์คํจ์น ํ์
} from '../../redux/store';
export default function ์ด์ฉ๊ณ (){
const dispatch = useDispatch<์คํ ์ด์์ ๋ด๋ณด๋ธ ๋์คํจ์น ํ์
>
dispatch(์ก์
?)
}
๋ฅ์คํธ์์๋ ssr์์๋ ์คํ ์ด๊ฐ ์ ์๋ํ๋๋ก ์ ์ฒด์ ์ธ ์คํ ์ด๋ฅผ ๋ค๋ฃจ๋ ๋ถ๋ถ์ด ํ์ํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด ์น๊ตฌ๋ค์ ๋ค ๋ฌถ์ด์ฃผ๋ ๋ฅ์คํธ๋ฅผ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์์ต๋๋ค.
์ค์นํ ํจํค์ง
๋ฆฌ๋์ค ํ๊ฒฝ์ ํ๋ ์ฌ๋ ๋๋ฆ(?)์ด๊ธฐ์ ์์ ์ด ํธํ๋๋ก ๊ตฌ์กฐ๋ฅผ ์ ์์ฑํ๋ฉด ๋ฉ๋๋ค. ํ์์ ์ธ ๋ถ๋ถ๋ง ์์ผ๋ฉด ๋ฉ๋๋ค.
ํ์ฌ ํ๊ฒฝ์ ์๋์ ๊ฐ์ต๋๋ค. (์์ง ์๋๋๋ ๊ฑด์ง ํ ์คํธํ์ง ๋ชปํด์ ๋์ค์ ๋ฐ๋ ๊ฐ๋ฅ์ฑ๋ ์์ต๋๋ค.)
stores
ใด modules
ใด slices
ใด ๊ฐ slices.ts
ใดindex.ts
ใดindex.ts
stores/index.ts
์ ์ฒด์ ์ธ ์คํ ์ด์ ๋๋ค. ๋ฏธ๋ค์จ์ด๋ก๋ logger ๋ฃ์ด๋์ต๋๋ค.
import { configureStore } from '@reduxjs/toolkit';
import { createWrapper } from 'next-redux-wrapper';
import logger from 'redux-logger';
import counterSlice from './modules/slices/counter';
const makeStore = () => {
const store = configureStore({
reducer: {
//๋ฆฌ๋์ ์ด๋ฆ: slice์ด๋ฆ
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
devTools: process.env.NODE_ENV !== 'production',
});
return store;
};
const wrapper = createWrapper(makeStore);
export default wrapper;
stores/modules/index.ts
๋ฅ์คํธ์์ ์คํ ์ด์ ์ ๊ทผํ ์ ์๋๋ก ์ค์ ํ๋ ๊ณณ์ ๋๋ค. ์์ธํ ๊ฑด ์์ ๋ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค!
import { AnyAction, CombinedState, combineReducers } from '@reduxjs/toolkit';
import { HYDRATE } from 'next-redux-wrapper';
import test from './slices/counter';
const reducer = (
state: CombinedState<{ test: { value: number } }>,
action: AnyAction,
) => {
switch (action.type) {
case HYDRATE:
return action.payload;
default:
return combineReducers({
test,
})(state, action);
}
};
export default reducer;
stores/modules/slices/[์ด์ฉ๊ณ ].ts
์ก์ ๊ณผ ๋ฆฌ๋์๋ฅผ ์ ์ํ๋ฉด๋ฉ๋๋ค. ๋ชจ๋ ์ฌ๋ผ์ด์ค๋ฅผ ํ ๊ณณ์ ๋ชจ์๋ ๋๊ณ , ๋ฐ๋ก๋ฐ๋ก ํ์ผ๋ก ๋ถ๋ฆฌํ์ฌ๋ ๋ฉ๋๋ค.
_app.tsx
ํ
๋ง์ฒ๋ผ _app.tsx์ ์ ์ฉํด์ค์ผ ์ ์ญ์์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
stores/index.ts
์์ ๋ง๋ค๊ณ ๋ด๋ณด๋ธ wrapper๋ฅผ ์ฌ์ฉํ๋ฉด ์ฝ๊ฒ ์ ์ฉํ ์ ์๋ ๊ฒ ๊ฐ์ต๋๋ค.
export default wrapper.withRedux(MyApp);
exportํ๋ ๋ถ๋ถ์ ์ด๋ ๊ฒ ์ฐ๊ธฐ๋ง ํ๋ฉด๋ฉ๋๋ค.?
์ปดํฌ๋ํธ์์ ์ฌ์ฉ
์ด ๋ถ๋ถ์ด Next๋ฅผ ์ฌ์ฉํ์ง ์์ ๋์ ์ฐจ์ด๊ฐ ๋ฉ๋๋ค. csr์ผ ๋๋ ๋์ผํ๊ฒ ์ฌ์ฉํ๋ฉด ๋๋ ๊ฒ ๊ฐ์ง๋ง, Ssr๋ก ์ฐ๋ ค๋ฉด ์๋์ ๊ฐ์ด ์ฌ์ฉํด์ผํ๋ค๊ณ ํฉ๋๋ค.
export const getServerSideProps = wrapper.getServerSideProps((store) => async({req, res, ...ets)} => {
await store.dispatch(//์ฌ๊ธฐ์ ๋ฃ๊ธฐ);
return { props: {} } ;
});
context api๋ ๋น๊ตํ์ฌ ๋ฆฌ๋์ค๊ฐ ์ข์ ์ . ๋น๋๊ธฐ์ฒ๋ฆฌ๋ฅผ ์์ฃผ์์ฃผ ์ฝ๊ฒ ํ ์ ์์ต๋๋ค. redux-toolkit์ด๋๋ ์ข์ต๋๋ค. ๋์ extraReducers ์ ์ํด์ค์ผํฉ๋๋ค.
์ด ์์๋ ๋ก๊ทธ์ธ๊น์ง ๋ง์ ๋จ๊ณ๊ฐ ํ์ํ ๋ ์ฌ์ฉํ๋ ์์์ ๋๋ค.( aํ ํฐ ๋ฐ๊ณ , ๊ทธ aํ ํฐ์ผ๋ก ์์ธ์ค ํ ํฐ ๋ฐ๊ณ ...๋ญ์ด๋ฐ ๊ฒฝ์ฐ!)
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import type { PayloadAction } from '@reduxjs/toolkit';
import {
postAccessToken,
postRequestToken,
postSessionId,
} from '../../apis/auth';
import { RootState } from '../store';
export const postLogin = createAsyncThunk(
'user/login',
async (email: string, thunkAPI) => {
const data = {
email: '',
requestToken: '',
accessToken: '',
sessionId: '',
};
try {
if (localStorage.getItem('requestToken')) {
const accessTokenResult = await postAccessToken();
const sessionIdResult = await postSessionId(
accessTokenResult.access_token,
);
return {
...data,
email,
requestToken: localStorage.getItem('requestToken'),
accessToken: accessTokenResult.access_token,
sessionId: sessionIdResult.session_id,
};
}
const result = await postRequestToken();
return { result };
} catch (err) {
return thunkAPI.rejectWithValue(await err);
}
},
);
export interface User {
loading: boolean;
email: string;
requestToken: string;
accessToken: string;
sessionId: string;
}
const initialState = {
loading: false,
email: '',
requestToken: '',
accessToken: '',
sessionId: '',
};
export const loginSlice = createSlice({
name: 'user',
initialState,
reducers: {},
///์ฌ๊ธฐ ์๋๋ก extraReducers๊ฐ ์ถ๊ฐ๋ฉ๋๋ค.
extraReducers: {
[postLogin.pending.type]: (state) => {
return { ...state, loading: true };
},
[postLogin.fulfilled.type]: (state, action: PayloadAction<User>) => {
state.loading = false;
state.email = action.payload.email;
state.requestToken = action.payload.requestToken;
state.accessToken = action.payload.accessToken;
state.sessionId = action.payload.sessionId;
},
[postLogin.rejected.type]: (state) => {
return { ...state };
},
},
});
export const userSelector = (state: RootState) => {
return state.userlogin;
};
export default loginSlice.reducer;
React Redux Toolkit ์ฌ์ฉํ๊ธฐ
๊ณต์๋ฌธ์
[Next.js] Next.js + redux toolkit ๊ธฐ๋ณธ ์ธํ