๋ฆฌ๋์ค๋ ์ฌ๋ฏธ์์์ง๋ง ๋ฆฌ๋์ค ํดํท ๋๋ถ์ ์กฐ๊ธ ์ฌ๋ฐ์ด์ก๋ค. ์ ๋ง ๋คํ์ด๋ค. ๐
npm install @reduxjs/toolkit react-redux
๋๋
yarn add @reduxjs/toolkit react-redux
์ค์น๋ถํฐ ๊ฐํธํ๋ค. ๊ณผ๊ฑฐ์ redux, react-redux ๋ ํ์ํ๋ค๋ฉด(๊ฑฐ์ ํ์์ ) devtools, saga, thunk, actions ๋ฑ๋ฑ ๋ค์ํ ์ปค์คํฐ๋ง์ด์ง์ด ๊ฐ๋ฅํ๋ค.
์ฒ์ ์ฌ์ฉํด๋ณธ๋ค๋ฉด ํํ ์ด์ง๋ฌ์์ ๋๋๋ค.
์ด๋ฐ ์์ ๋ค์ด ๋ชจ๋ ๊ฐํธํด์ก๋ค๋ ์ ๋ง ๊ฐ์ฌํ๋ค.
๋ฆฌ๋์ค ํดํท์ ๋ด๋ถ์ ์ผ๋ก thunk์ devtools๋ฅผ ์ ๊ณตํ๋ค.
// store.js
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./counterSlice";
export default configureStore({
reducer: {
counter: counterReducer,
},
});
๊ธฐ์กด์ ์ฌ์ฉ๋ฒ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก store๋ฅผ ๋ง๋ค์ด์ผํ๋ค. ์ด๋ createStore๊ฐ ์๋ configureStore๋ฅผ ์ฌ์ฉํ๋๋ฐ ์ฌ์ฉ๋ฒ์ด ์กฐ๊ธ ๋ค๋ฅด๋ค.
๊ธฐ์กด์๋ combineReducers๋ก ํตํฉํด ํ๋์ reducer๋ง ์ ๊ณตํ๋ค๋ฉด ์ด์ ๋ reducer ๊ฐ์ฒด์ ๊ทธ๋ฅ ์ถ๊ฐํ๊ธฐ๋ง ํ๋ฉด ๋๋ค.
๊ทธ๋ฆฌ๊ณ ๊ธฐ๋ณธ์ ์ผ๋ก devtools๋ฅผ ๋ด์ฅํ๊ณ ์๋ค.
// counterSlice.js
import { createSlice } from "@reduxjs/toolkit";
const counterSlice = createSlice({
name: "counter",
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
๊ธฐ์กด์๋ actionCreator, reducer, initialState๋ฅผ ๊ฐ๊ฐ ๋ฐ๋ก ์ ์ธํ์ง๋ง createSlice๋ ํ๋ฒ์ ๊ด๋ฆฌํ ์ ์๋ค.
name์ action์ prefix ์ญํ ์ ํ๋ค.
ํ๋จ์ ๋ณด์ด๋ ๊ฒ๊ณผ ๊ฐ์ด counterSlice ๊ฐ์ฒด์ actions๋ก action Creator๋ฅผ ์ฐธ์กฐํ๊ณ , reducer๋ก reducer๋ฅผ ์ฐธ์กฐํ ์ ์๋ค.
๊ด๋ จ์๋ ๊ฒ๋ค์ ํ๋์ ๋ฌธ์์์ ๋ค๋ฃจ๋ Ducks ํจํด์ ๋ฐ๋ฅด๋ ๊ฒ ๊ฐ๋ค.
์ฌ์ฉ์ ๊ธฐ์กด๊ณผ ๊ฐ๋ค. useSelector๋ฅผ ์ด์ฉํด ๊ตฌ๋ ํ๊ณ , useDispatch ํ ์ ์ด์ฉํด ์ก์ ์ ๋ฐ์์ํจ๋ค.
๋น๋๊ธฐ ์ฒ๋ฆฌ๋ ์์ฃผ ํธ๋ฆฌํด์ก๋ค. saga์ thunk๋ฅผ ํตํ ๋น๋๊ธฐ์ฒ๋ฆฌ๋ฅผ ํ๋ฉด์ ๊ต์ฅํ ์ด๋ ค์์ ๋๊ผ์๋๋ฐ, ๊ทธ์ ๋นํด์๋ ์์ฃผ ํธํด์ก๋ค๊ณ ํ ์ ์๋ค.
export const login = createAsyncThunk(
"auth/login",
async ({ username, password }) => {
const response = await authAPI.login({ username, password });
return response.data;
}
);
const authSlice = createSlice({
name: "auth",
initialState: { user: null, error: null, loading: false },
reducers: {
logout: (state) => {
state.user = null;
},
},
extraReducers: {
[login.pending]: (state) => {
state.loading = true;
},
[login.fulfilled]: (state, action) => {
state.loading = false;
state.user = action.payload;
},
[login.rejected]: (state) => {
state.loading = false;
state.error = "error!!!";
},
},
});
createAsyncThunk๋ thuckActionCreator๋ฅผ ๋ฐํํ๋ค. ์ด๊ฒ์ ์์ ์์์์ ๋ณด์ด๋ ๊ฒ์ฒ๋ผ pending, fulfilled, rejected์ ๋ฐ๋ผ ๋ค๋ฅธ action๋ค์ ์ฐธ์กฐํ๋ plain object๋ค.
auth/login/pending ๊ฐ์ ํํ๋ก ์ก์ ์ ๋ง๋ค์ด ์ค๋ค๊ณ ์๊ฐํ๋ฉด ๋๋ค.
dispatch๋ก thnkAction์ ํธ์ถํ ๋ ๋ฐ์ํ๋ ์ผ์ ๋ค์๊ณผ ๊ฐ๋ค.
์์ ์ด ์์ฃผ ๊ฐํธํด์ก๋ค. ์ด๋์ ์ถ์ํ ๊ณ์ธต์ ์ ๊ณตํ๋ ๊ฑธ ์ฌ๋ฌ ํ๋ก๊ทธ๋๋ฐ ๊ธฐ๋ฒ์์ ๊ฐ์กฐํ๋ ๊ฒ์ผ์ง๋ ๋ชจ๋ฅด๊ฒ ๋ค.
export const loginUser = createAsyncThunk(
"user/loginUser",
async ({ id, password }, { rejectWithValue }) => {
try {
const response = await authAPI.login({ id, password });
return response.data;
} catch (e) {
return rejectWithValue(e.message);
}
}
);
// ์๋ต
extraReducers: {
[actions.loginUser.pending]: (state) => {
state.isLoading = true;
},
[actions.loginUser.fulfilled]: (state, { payload }) => {
state.name = payload.name;
state.id = payload.id;
state.isLogin = true;
state.isLoading = false;
},
[actions.loginUser.rejected]: (state, action) => {
state.isLoading = false;
state.error = action.payload;
},
}
payload creator๋ ๋ ๋ฒ์งธ ์ธ์๋ก thunkAPI๋ฅผ ๋ฐ์ ์ ์๋ค. ์ด๋ thunkAPI.rejectWithValue๋ฅผ ์ด์ฉํ๋ฉด rejected ์ก์ ์์ payload๋ก ์ง์ ์๋ฌ๋ฅผ ์ค์ ํ ์ ์๋ค.
thunkAPI.rejectWithValue๋ฅผ ์ฌ์ฉํ์ง ์์ผ๋ฉด ์ผ๋ฐ์ ์ธ ์๋ฌ๋ฅผ rejected ์ก์ ์์ ์ฌ์ฉํ๋ค.
๋ฆฌ๋์ค ํดํท์ ์ฐธ ๊ฐ์ฌํ ํจํค์ง๋ค.
์์ง์ ์๋ จ๋๊ฐ ๋ฎ์ ๋ฅ์๋ฅ๋ํ๊ฒ ํ์ฉํ ์๋ ์๊ฒ ์ง๋ง ๊ธฐ์กด์ ์ฝ๋๋ฅผ ๋ฆฌํฉํ ๋งํ๊ฑฐ๋ ๋ค์ํ ์์ ๋ฅผ ์ ํด๋ณด๋ฉฐ ์๋ จ๋๋ฅผ ์์์ผ๊ฒ ๋ค.
๋ฆฌ๋์ค๊ฐ ์กฐ๊ธ์ ์ข์์ง ๊ฒ ๊ฐ๋ค.
createAsyncThunk๋ ํญ์ ์ดํ๋ ํ๋ก๋ฏธ์ค๋ฅผ ๋ฐํํ๋ค๋ ๋ง์ด ์๋๋ฐ ์ด ๋ถ๋ถ์ ์กฐ๊ธ ๋ ์์๋ด์ผ๊ฒ ๋ค.