- yarn create react-app (ํ๋ก์ ํธ ์ด๋ฆ) --template redux
- yarn add @reduxjs/toolkit redux-devtools-extension
- yarn add @types/react-redux //ํ์ ์คํฌ๋ฆฝํธ
- Redux Toolkit์๋ Redux ์ฝ๋๋ฅผ ๋จ์ํํ๋๋ฐ ๋์์ด ๋๋ ๋ช ๊ฐ์ง ๊ธฐ๋ฅ ์ค ์ฒซ ๋ฒ์ฌ๊ฐ
configureStore
์ด๋ค. - ์ผ๋ฐ์ ์ผ๋ก createStore()๋ฅผ ํธ์ถํ๊ณ root reducer ํจ์๋ฅผ ์ ๋ฌํ์ฌ redux store๋ฅผ ๊ตฌ์ฑํ๋ค.
- Redux Toolkit์ createStor()๋ฅผ ๋ํํ configureStore() ํจ์๋ฅผ ์ ๊ณตํ๊ณ ์ด ํจ์๋ ๊ธฐ๋ณธ์ ์ผ๋ก createStore()๊ณผ ๋์ผํ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค. ํ์ง๋ง configureStore()๋ store๋ฅผ ์์ฑํ๋ ๋จ๊ณ์์ ๋ช ๊ฐ์ง ์ ์ฉํ ๊ฐ๋ฐ ๋๊ตฌ๊ฐ ์ค์ ๋๋๋ก ํ๋ค.
- configureStore()๋ ์ฌ๋ฌ ๊ฐ์ ์ธ์ ๋์ ์ด๋ฆ์ด ์ง์ ๋ ํ๋์ object๋ฅผ ์ธ์๋ก ๋ฐ์ผ๋ฏ๋ก, reducer ํจ์๋ฅผ reducer๋ผ๋ ์ด๋ฆ์ผ๋ก ์ ๋ฌํด์ผ ํ๋ค.
// Before:
const store = createStore(counter);
// After:
const store = configureStore({
reducer: counter,
});
// Example
const reducer = {
...contractsReducer,
(...)
}
const rootReducer = combineReducers(reducer);
export const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: (...),
},
})
.prepend()
// prepend and concat calls can be chained
.concat(middlewares),
devTools: process.env.NODE_ENV !== "production",
});
- createAction์ ์ก์ ํ์ ๋ฌธ์์ด์ ์ธ์๋ก ๋ฐ๊ณ , ํด๋น ํ์ ์ ์ฌ์ฉํ๋ ์ก์ ์์ฑ์ํจ์๋ฅผ ๋ฐํํ๋ค.
// Before: ์ก์
type๊ณผ ์์ฑํจ์๋ฅผ ๋ชจ๋ ์์ฑ
const INCREMENT = "INCREMENT";
function incrementOriginal() {
return { type: INCREMENT };
}
console.log(incrementOriginal()); // {type: "INCREMENT"}
// After: createAction ์ฌ์ฉ
const incrementNew = createAction("INCREMENT");
console.log(incrementNew()); // {type: "INCREMENT"}
- createAction์ ์ฌ์ฉํ์ฌ counter ์์ ๋จ์ํ
const increment = createAction("INCREMENT");
const decrement = createAction("DECREMENT");
function counter(state = 0, action) {
switch (action.type) {
case increment.type:
return state + 1;
case decrement.type:
return state - 1;
default:
return state;
}
}
- if๋ฌธ๊ณผ ๋ฐ๋ณต๋ฌธ์ ํฌํจํ์ฌ reducer์์ ์ํ๋ ์กฐ๊ฑด ๋ ผ๋ฆฌ๋ฅผ ์ฌ์ฉํ ์ ์์ง๋ง, ๊ฐ์ฅ ์ผ๋ฐ์ ์ธ ๋ฐฉ๋ฒ์ action.type ํ๋๋ฅผ ํ์ธํ๊ณ ๊ฐ ์ ํ์ ๋ํด ์ ์ ํ ๋ก์ง์ ์ํํ๋ ๊ฒ์ด๋ค.
- reducer๋ ์ด๊ธฐ ์ํ๊ฐ์ ์ ๊ณตํ๊ณ , ํ์ฌ ์ก์ ๊ณผ ๊ด๊ณ์๋ ์ํ๋ ๊ทธ๋๋ก ๋ฐํํ๋ค.
- Redux Toolkit์๋
lookup Table
๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ์ฌ reducer๋ฅผ ์์ฑํ ์ ์๋ createReducer()๊ฐ ์๋ค. - createReducer() ๊ฐ์ฒด์ ๊ฐ ํค๋ redux์ ์ก์ type ๋ฌธ์์ด์ด๋ฉฐ ๊ฐ์ reducerํจ์์ด๋ค.
- ์ก์
type ๋ฌธ์์ด์ ํค๋ก ์ฌ์ฉํด์ผ ํ๋ฏ๋ก
ES6 object computer ์์ฑ
๊ตฌ๋ฌธ์ ์ฌ์ฉํ์ฌ type๋ฌธ์์ด ๋ณ์๋ก ํค๋ฅผ ์์ฑํ ์ ์๋ค. - computed ์์ฑ ๊ตฌ๋ฌธ์ ๋ด๋ถ์ ์๋ ๋ชจ๋ ๋ณ์์ ๋ํด
toString()
์ ํธ์ถํ๋ฏ๋ก.type
ํ๋์์ด ์ง์ ์ก์ ์์ฑ์ ํจ์๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
const increment = createAction("INCREMENT");
const decrement = createAction("DECREMENT");
const counter = createReducer(0, {
[increment]: (state) => state + 1,
[decrement]: (state) => state - 1,
});
- ์์ ๋ด์ฉ์ผ๋ก๋ ๋์์ง ์์ง๋ง, createSlice๋ก ๋ ํฐ ๋ณํ๋ฅผ ์ค ์ ์๋ค.
- createSlice ํจ์๋ ๊ฐ์ฒด์ reducer ํจ์๋ค์ ์ ๊ณตํ ์ ์๊ณ ์ด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ก์ ํ์ ๋ฌธ์์ด๊ณผ ์ก์ ์์ฑ์ ํจ์๋ฅผ ์๋์ผ๋ก ์์ฑํ๋ค.
- createSlice๋ ์์ฑ๋ reducer ํจ์๋ฅผ reducer๋ผ๋ ํ๋๋ฅผ ํฌํจํ๋
slice
๊ฐ์ฒด์actions
๋ผ๋ ๊ฐ์ฒด ๋ด๋ถ์์ ์์ฑ๋ ์ก์ ์์ฑํจ์๋ฅผ ๋ฐํํ๋ค.
const counterSlice = createSlice({
name: "counter",
initialState: 0,
reducers: {
increment: (state) => state + 1,
decrement: (state) => state - 1,
},
});
const store = configureStore({
reducer: counterSlice.reducer,
});
- ๋๋ถ๋ถ์ ๊ฒฝ์ฐ, ES6 ๋์คํธ๋ญ์ฒ๋ง ๊ตฌ๋ฌธ์ ์ด์ฉํ์ฌ ์ก์ ์์ฑ์ ํจ์์ reducer๋ฅผ ๋ณ์๋ก ์ฌ์ฉํ๊ธฐ๋ฅผ ์ํ๋ค.
export const { increment, decrement } = counterSlice.actions;
- createSlice ์ต์
- name: ์์ฑ ๋ action types๋ฅผ ์์ฑํ๊ธฐ ์ํด ์ฌ์ฉ๋๋ prefix
- initialState: reducer์ ์ด๊ธฐ ์ํ
- reducers: key๋ action type ๋ฌธ์์ด์ด ๋๊ณ ํจ์๋ ํด๋น ์ก์ ์ด dispatch๋ ๋ ์คํ๋ reducer์ด๋ค.
- ์๋ก,
todos/addTodo
์ก์ ์ด dispatch๋ ๋ addTodo Reducer๊ฐ ์ํ๋๋ค. - createSlice์ createReducer๋
immer library
์produce
๋ก ๋ํํ๋ค. ์ด๊ฒ์ ์ด ํจ์๋ฅผ ์ฌ์ฉํ๋ ๊ฐ๋ฐ์๋ ๋ฆฌ๋์ ๋ด๋ถ์ ์ํ๋ฅผ๋ณํํ๋
์ฝ๋๋ฅผ ์์ฑํ ์ ์์ผ๋ฉฐ, immer๋ ์ํ๋ฅผ ์์ ํ๊ฒ ๋ถ๋ณํ๊ฒ ๋ค๋ฃฐ ์ ์๋๋ก ์ฒ๋ฆฌํด์ค๋ค.(์ฆ, push ๊ฐ์ ๋ฉ์๋ ์ฌ์ฉ ๊ฐ๋ฅ)
- createSclie๋ ๋ค์๊ณผ ๊ฐ์ ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ค.
{
name: "todos",
reducer: (state, action) => newState,
actions: {
addTodo: (payload) => ({type: "todos/addTodo", payload}),
toggleTodo: (payload) => ({type: "todos/toggleTodo", payload})
},
caseReducers: {
addTodo: (state, action) => newState,
toggleTodo: (state, action) => newState,
}
}
- ๊ฐ ๋ฆฌ๋์๋ง๋ค ์ ์ ํ action ์์ฑ์์ action type์ ์๋์ผ๋ก ์์ฑํ๋ฏ๋ก ์ง์ ์์ฑํ์ง ์์๋ ๋๋ค.
createAsyncThunk
๋ฅผ ์ ์ธํ๊ฒ ๋๋ฉด ์ฒซ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ก ์ ์ธํ ์ก์ ์ด๋ฆ์ pending, fulfilled, rejected์ ์ํ์ ๋ํ action์ ์๋์ผ๋ก ์์ฑํด์ฃผ๊ฒ ๋๋ค.- AbortController๋ฅผ ์ง์ํ๊ธฐ ๋๋ฌธ์ thunk๋ฅผ ์ฌ์ฉํ์ฌ๋ api์ ๋ํ ์ทจ์ ์์ ์ด ๊ฐ๋ฅํ๋ค.
const fetchTodo = createAsyncThunk(
`todo/fetchTodo`, // ์ก์
์ด๋ฆ์ ์ ์ํด ์ฃผ๋๋ก ํฉ๋๋ค.
async (todoId, thunkAPI) => {
// ๋น๋๊ธฐ ํธ์ถ ํจ์๋ฅผ ์ ์ํฉ๋๋ค.
const response = await todoApi.fetchTodoInfo(todoId);
return response.data;
}
);
// fetchTodo.pending => todo/fetchTodo/pending
// fetchTodo.fulfilled => todo/fetchTodo/fulfilled
// fetchTodo.rejected => todo/fetchTodo/rejected