๋ฐ์ดํฐ ๊ตฌ์กฐ
์ํ๋ ๋ฐฐ์ด ํ์ ์ด๊ณ , ๋ฐฐ์ด์ ์์๋ ๊ฐ object ๊ฐ์ฒด์ด๋ค.
const initialState = [{id: '1', isDone: false }, {id: '2', isDone: false }, ...]
๋ชฉํ
์์ ๊ฐ์ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง๊ณ ์๋ ์ ์ญ ์ํ state ๋ฐฐ์ด์์ ํด๋นํ๋ ์์๋ฅผ ๋จผ์ ์ฐพ๊ณ , isDone ์์ฑ์ ๋ฐ๊พธ๊ณ ์ถ๋ค.
redux toolkit์ ์ฌ์ฉํด์ ์ํ๋ฅผ ๊ด๋ฆฌํ๋๋ฐ, ๋ฐฐ์ด ์ ํน์ ์์์ ์์ฑ isDone์ ์ ์ ๊ฐ ๋๋ฅด๋ checkbox ์ด๋ฒคํธ ๊ฐ๋๋ก ๋ฐ๊ฟ์ผํ๋ค.
UI ์ฝ๋
์ ์ ๊ฐ ์ฒดํฌํ๋ ์ฒดํฌ๋ฐ์ค์ event.target.checked ๊ฐ์ ๋ฐ์์ rtk ์ฌ๋ผ์ด์ค๋ก dispatchํ๋ ์ฝ๋
export interface Todo {
id: string;
isDone: boolean;
}
interface Props {
data: Todo;
}
export const TodoBox = ({data}: Props) => {
const handleChangeCheckBox = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(updateTodoCheck({ id: data.id, isDone: e.target.checked }));
};
return (
<Box sx={{ border: 'solid 1px' }}>
<Checkbox
checked={data.isDone}
onChange={handleChangeCheckBox}
inputProps={{ 'aria-label': 'controlled' }}
/>
)
}
์ํ๊ฐ์ ์ฌ๋ผ์ด์ค๋ก dispatch ํ๋ฉด, ์ดํ์ ์ฌ๋ผ์ด์ค์ ์๋ ๋ฆฌ๋์ ํจ์๊ฐ ์๋ํ๋ฉด์ ํด๋น ์์์ isDone ์ํ๊ฐ์ ์
๋ฐ์ดํธํ๊ฒ ๋๋ค.
export const todoSlice = createSlice({
name: 'todo',
initialState,
reducers: {
updateTodoCheck: (state, action) => {
//๋ฐฉ๋ฒ1)2)3) ํจ์๊ฐ ์์นํ๋ ๊ณณ
}
}
})
ํ๊ธฐ ๋ช ์ํ ๋ฐฉ๋ฒ 1, 2, 3๋ค์ ๋ชจ๋ ์ฌ๋ผ์ด์ค์ reducers ํจ์ ์์ ๊ตฌํ๋ ์ฝ๋์ด๋ค.
๋ฐฉ๋ฒ 1) ๋ฐฐ์ด ์์ ์ฐพ์์ ๊ฐ ์ฌํ ๋น
๐ข
updateTodoCheck: (
state,
action: PayloadAction<{ id: string; isDone: boolean }>,
) => {
const targetTodoIdx = state.findIndex(
(el) => el.id === action.payload.id
);
state[targetTodoIdx].isDone = action.payload.isDone;
}
์๊ฐ๋๋ ๊ฐ์ฅ ๊ฐ๋จํ ๋ฐฉ๋ฒ์
๋จผ์ ์์ ํ๊ณ ์ถ์ ๋ฐฐ์ด ์์์ ์ธ๋ฑ์ค๋ฅผ ์ฐพ์๋ด๋ ๊ฒ์ด์๋ค.
๊ทธ๋ฆฌ๊ณ ๊ทธ ๋ฐฐ์ด์ isDone ํค๊ฐ์ dispatch ํจ์ ์ฃผ๋ payload.isDone ๊ฐ์ผ๋ก ์ฌํ ๋นํ๋ ๊ฒ์ด์๋ค.
Redux์ ์์น์ ๋ฐ๋ผ ์ํ๊ฐ์ ์ฝ๊ธฐ ์ ์ฉ์ด์ด์ผํ๊ณ , ๋ฆฌ๋์ ํจ์๋ ์์ํจ์๋ก ์์ฑ๋์ด์ผ ํจ์ผ๋ก ์ด์ ์ํ๋ฅผ ๋ณ๊ฒฝํ๋ฉด ์๋๊ณ ์๋ก์ด ์ํ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ ๋ฐํํด์ผ ํจ์ผ๋ก ์์ ์์ ์ฒ๋ผ ๋ฐฐ์ด์ ์ธ๋ฑ์ค๋ฅผ ์ฐพ์์ ์ง์ ๊ฐ์ ์ฌํ ๋นํ๋๊ฒ ์๋ ๊ฒ ๊ฐ์ง๋ง...
Redux toolkit ๋ฆฌ๋์์์ ์ด ๋ฐฉ๋ฒ์ ๋ฌธ์ ์์ด ์์ฃผ ์ ์๋์ ํ๋ค.
๊ทธ ์ด์ ๋ ๋ฆฌ๋์คํดํท์์ `createReducer` ๋ฐ `createSlice`๋ฅผ ์ฌ์ฉํด์ reducer ํจ์๋ฅผ ์์ฑํ๋ฉด ๋ด๋ถ์ ์ผ๋ก Immer๋ผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ตฌํ์ด ๋์ด์๊ธฐ ๋๋ฌธ์ mutableํ๊ฒ ์๋ณธ์ ๋ณ๊ฒฝํ๋ ํจ์๋ฅผ ์์ฑํ ์ง๋ผ๋ ๋ถ๋ณ์ฑ์ด ์ ์ง๋๋ค. ์ฆ, ์๋ณธ ์ํ๋ ๋ณ๊ฒฝ๋์ง ์๊ฒ ๋ด๋ถ์ ์ผ๋ก ๊ตฌํ๋์ด์๋ค.
์๋จ ์ฝ๋์์๋ state ํน์ ์์์ ์๋ก์ด ๊ฐ์ ์ฌํ ๋นํ๋ฏ๋ก์ ์๋ณธ๋ฐฐ์ด์ธ state๊ฐ ๋ณ๊ฒฝ๋์ง๋ง, ๋ด๋ถ์ ์ผ๋ก Immer๊ฐ ๊ตฌํ๋์ด์๊ธฐ ๋๋ฌธ์ ๋ถ๋ณ ์ํ๊ฐ์ผ๋ก ๊ด๋ฆฌ๋๋ค.
๋ฐฉ๋ฒ 2) Object.assign ํ์ฉ
2-1) โ
updateTodoCheck: (
state,
action: PayloadAction<{ id: string; isDone: boolean }>,
) => {
return state.map((el) => {
if (el.id === action.payload.id) {
return Object.assign(el, {
isDone: action.payload.isDone,
});
}
return el;
});
},
statet ์ํ๊ฐ ์๋ณธ ๋ฐฐ์ด์ map์ผ๋ก loop ๋๋ฆฌ๋ฉด์ ๋ฆฌ๋์คํดํท์ action payload๋ก ์ฃ์ด์ค ๋ฐฐ์ด์ id๊ฐ๊ณผ state ์๋ณธ๋ฐฐ์ด์ id๊ฐ ๊ฐ์ ์์๋ฅผ ์ฐพ๋๋ค.
๊ทธ๋ฆฌ๊ณ Object.assign ์ ์ด์ฉํด์ ์๋ก์ด isDone ์์ฑ๊ฐ์ผ๋ก ํ ๋นํ๋ค.
Object.assign(๋ณ๊ฒฝํ ๊ฐ์ฒด, ์๋ก์ด ๊ฐ์ผ๋ก mergeํ ๊ฐ์ฒด)
ํ์ง๋ง, Object.assign ๋ฉ์๋๋ฅผ ์์ ๊ฐ์ด ๋ณ๊ฒฝํ ๊ฐ์ฒด๋ฅผ target ์๋ฆฌ ๋งจ ์ ์ธ์๋ก ์ ์ด์ฃผ๋ฉด, ๊ทธ ๋๋ ๊ทธ ๋ณ๊ฒฝํ target ๊ฐ์ฒด์ ๊ฐ์ ์๋ก์ด ๊ฐ์ผ๋ก ๋ฎ์ด์์ด ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ์๋ณธ ๋ฐ์ดํฐ state๊ฐ ๋ณ๊ฒฝ๋๋ค.
์ด๋ Object.assgin ๋ฉ์๋๋ mutable๋ก ์๋ณธ ๋ฐ์ดํฐ๋ฅผ ๋ณ๊ฒฝ์ํจ๋ค.
๊ทธ๋ฆฌ๊ณ ์ด ๊ฒฝ์ฐ redux toolkit ์ฌ๋ผ์ด์ค์์ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
errors.ts:53 Uncaught Error: [Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft.
โ๏ธ์๋ฌ ๋ด์ฉ
์๋ก ๋ณ๊ฒฝ๋ ๋ฐ์ดํฐ๊ฐ ๋ฆฌํด๋จ๊ณผ ๋์์ ์๋ณธ ๋ฐ์ดํฐ๊ฐ ์์ ๋จ. ์๋ก์ด ๊ฐ์ ๋ฆฌํดํ๋์ง ์๋๋ฉด ์๋ณธ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๋์ง ๋ ์ค์ ํ๋๋ง ํด๋ผ
2-2) ๐ข
updateTodoCheck: (
state,
action: PayloadAction<{ id: string; isDone: boolean }>,
) => {
state.map((el) => { // <------------------- ์ฌ๊ธฐ! return ์ ๋นผ์ค๋ค
if (el.id === action.payload.id) {
Object.assign(el, { isDone: action.payload.isDone });
}
return el;
});
}
์๋ฌ๋ฉ์์ง๋๋ก state์ ์๋ณธ ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋์๊ณ , ๊ทธ ๋ณ๊ฒฝ๋ ๊ฐ์ ๋ฐํ๊น์ง ํ๋ ๊ฒ์ผ๋ก Immer์์ ์๋ฌ๋ฅผ ๋ด๋ ๋ ์ค์ ํ๋๋ฅผ ํํ๋ค.
ํจ์ ์์ฒด์ returnํค์๋๋ฅผ ๋นผ์ค๋ค.
์ ์์ ์์ state๋ ์๋ณธ ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋๊ณ ๋ ํ ์์ ๋ ๊ฐ์ ๊ฐ์ง๊ณ ์์ผ๋ฏ๋ก state๋ ์๋ณธ๋ฐ์ดํฐ๋ฅผ ์์ ํ ๊ฐ์ ์ด์ ๊ฐ์ง๊ณ ์๋ค.
์ด์ ์๋ก๋ง๋ค์ด์ง ๋ฐ์ดํฐ๋ฅผ ๋ฆฌํดํ์ง์๊ณ ์๋ณธ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ฐ๋ง ๊ฐ์ง๊ณ ์๊ธฐ๋๋ฌธ์ ์๋ฌ๋ ์์ด์ง๋ค.
๊ทธ๋ฆฌ๊ณ 2-2) ๋ฐฉ๋ฒ ๋ํ ์๋ณธ๋ฐฐ์ด์ด mutate๋์ด ์๋ณธ ๋ฐ์ดํฐ๊ฐ์ด ๋ณํ์ง๋ง Immer๊ฐ ๋ด๋ถ์ ์ผ๋ก ๋ถ๋ณ๊ฐ์ผ๋ก ์์์ ๊ด๋ฆฌํด์ค๋ค.
2-3) ๐ข
updateTodoCheck: (
state,
action: PayloadAction<{ id: string; isDone: boolean }>,
) => {
return void state.map((el) => { // <-------- ์ฌ๊ธฐ! return ํ void ํค์๋๋ฅผ ๊ฐ์ด ๋ฃ์ด์ค๋ค.
if (el.id === action.payload.id) {
Object.assign(el, { isDone: action.payload.isDone });
}
return el;
});
}
๋๋ 2-1) ์์ ์์ return ์ดํ์ void ํค์๋๋ฅผ ์ถ๊ฐํ๋ฉด ์๋ฌ์์ด ์ ์๋ํ๋ค.
return ๋ฐ๋ก ๋ค์์ void ํค์๋๋ฅผ ์จ์ฃผ๋ฉด ๋ฐํ๋๋ ๊ฐ์ ๊ฑด๋๋๊ฒ ํ๋ ๊ฒ์ด๋ค. ์ฆ, ์ ์์ ์์๋ ๋ฐํ๊ฐ์ ๊ฑด๋๋์ฐ๊ณ mutateํ state ๋ฐ์ดํฐ ์์ฒด๋ฅผ reducer ํจ์์ ๊ฒฐ๊ณผ๊ฐ์ผ๋ก ๊ฐ์ง๊ณ ์๋ ๊ฒ์ด๋ค.
๊ทธ๋ ๊ฒ๋๋ฉด 2-2) ์์ ์ ๋์ผํ๊ฒ ์ด reducer๋ ์๋ณธ์ด ๋ณ๊ฒฝ๋ state๋ฅผ ๊ฐ์ง๊ณ ์๋ ๊ฒ์ด๋ค.
๐โ๏ธ void ํค์๋๋ฅผ ๋ฃ๋ ์ด์ ?
Immer๊ฐ ์๋๋ mutation(๋ณ๊ฒฝ๋ ์๋ณธ ๋ฐ์ดํฐ)์ ์๋ก ๋ฆฌํด๋ ๋ฐ์ดํฐ ๊ฐ์ค ๋ฌด์จ ๊ฐ์ ๊ฒฐ๊ณผ๋ก ์ฌ์ฉํด์ผ๋์ง ๋ชฐ๋ผ์ ์๋ฌ๋ฅผ ๋ฐ์ ์ํค๊ธฐ ๋๋ฌธ์ด๋ค.
์๋ฌ๋ฉ์์ง๋ฅผ ๋ค์ ๋ ์ฌ๋ ค๋ณด์.
์๋ก ๋ณ๊ฒฝ๋ ๋ฐ์ดํฐ๋ฅผ ๋ฆฌํดํ๋์ง, ์๋ณธ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๋์ง ๋ ์ค์ ํ๋๋ง ํ๋ผ.
์ ์์ ์ ๊ฒฝ์ฐ๋ state ๊ฐ์ด Object.assign๋ฉ์๋๋ก ์๋ณธ ๋ฐ์ดํฐ๊ฐ mutate ๋ณ๊ฒฝ๋์๊ธฐ ๋๋ฌธ์ ์๋ก ๋ณ๊ฒฝ๋ ๋ฐฐ์ด ๋ฐ์ดํฐ๋ฅผ returnํ๋ ๊ฑธ ํฌ๊ธฐํ๊ณ ๋ณ๊ฒฝ๋ ๊ฐ ์์ฒด๋ฅผ ๊ฒฐ๊ณผ๊ฐ์ผ๋ก ๊ฐ์ ธ๊ฐ๋ ๊ฒฉ์ด๋ค.
2-4) ๐ข
Object.assgin() ๋ฉ์๋๋ผ๊ณ ๋ชจ๋ ์๋ณธ๊ฐ์ ๋ณ๊ฒฝ์ํค๋ mutable ๋ฉ์๋๊ฐ ์๋๋ค.
์๋ก์ด ๊ฐ์ฒด๋ฅผ ๋ณต์ฌํด์ ์๋ณธ๊ฐ์ด ๋ณ๊ฒฝ๋์ง ์๋๋ก ๋ง๋ค ์๋ ์๋ค.
updateTodoCheck: (
state,
action: PayloadAction<{ id: string; isDone: boolean }>,
) => {
//state๋ฅผ immutableํ๊ฒ ์์
return state.map((el) => {
if (el.id === action.payload.id) {
return Object.assign({}, el, {
isDone: action.payload.isDone,
});
}
return el;
});
},
์ ์์ ๋ ๋ง์ฐฌ๊ฐ์ง๋ก ๋ฐฐ์ด state ๋ฐ์ดํฐ๋ฅผ ๊ณ ์ฐจํจ์ map์ผ๋ก loop ๋๋ฆฌ๋ฉด์, ์กฐ๊ฑด์ ๋จผ์ ๋ถ๊ธฐํ๋ค.
payload ์ก์ ์ผ๋ก ๋ฐ๋ id ์ state ๋ฐฐ์ด ๋ฐ์ดํฐ์ค ๊ฐ์ ๊ฐ์ด ์๋์ง?
์์ผ๋ฉด? ๊ทธ ๊ฐ์ฒด ์์ ๊ฐ์ isDone ๊ฐ์ ๋ณ๊ฒฝํ๋ค.
์ด๋, Object.assign ๋ฉ์๋์์ ๊ฐ์ฅ ์ฒซ๋ฒ์งธ ์ธ์์ธ target ์ธ์์ ๋น ๊ฐ์ฒด๋ฆฌํฐ๋ด {}์ ๋ฃ์ด์ฃผ๋ฉด {}๋ผ๋ ์๋ก์ด ๊ฐ์ฒด๊ฐ ๋ง๋ค์ด์ง๊ณ ์๋ก์ด ๊ฐ์ฒด๊ฐ ๋ฆฌํด๋๋ค๋ ๋ป์ด๋ค. ๊ทธ๋ฆฌ๊ณ ๋์ ๋น๊ฐ์ฒด์ el์ด๋ผ๋ ์์๊ฐ ๋ณํฉ๋๊ณ , ๋ ๋ด๊ฐ ์ํ๋ isDone ํ๋กํผํฐ ๊ฐ๋ ๋ณํฉ์ด ๋๋ค.
์ฆ, Object.assign({}, ์๋ก ๋ฐ๊พธ๋ ค๋ ๊ฐ์ฒด)๋ฅผ ํ์ฉํ๋ฉด, ์๋ณธ ์์ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๋ ๊ฒ์ด์๋๊ณ , ์๋ก์ด ๊ฐ์ฒด์ ๋ณต์ ํ๋ ๊ฒ์ด ๋์ด immutable ํ๊ฒ ์์ ํ ์ ์๋ค.
๋ฐฉ๋ฒ 3) spread ์ฐ์ฐ์ ํ์ฉ
๐ข
updateTodoCheck: (
state,
action: PayloadAction<{ id: string; isDone: boolean }>,
) => {
return state.map((el) => {
if (el.id === action.payload.id) {
return {
...el,
isDone: action.payload.isDone,
};
}
return el;
});
},
๋ง์ฐฌ๊ฐ์ง๋ก ๋ฐฐ์ด state๋ฅผ loop ๋๋ฆฌ๋ฉด์ ์ฐพ๋ id ๊ฐ์ด ์๋์ง ํ์ธํ๋ ค๊ณ ์กฐ๊ฑด์ ๋ถ๊ธฐํ๋ค.
์ฐพ๋ id ๊ฐ์ด ์์ผ๋ฉด?
์๋ก์ด ๊ฐ์ฒด ๋ฆฌํฐ๋ด {}์ ์ ์ด์ฃผ์ด spread ์ฐ์ฐ์๋ก ์๋ el ๊ฐ์ ๋ณต์ฌํด์ค๊ณ , ๊ฑฐ๊ธฐ์ payload๋ก ๋ฐ๋ isDone ๊ฐ์ ๋ฎ์ด์์ด ๊ฐ์ ๋ฆฌํดํ๋ค.
์์ผ๋ฉด? ๊ทธ๋ฅ ์๋์ el ์์ ๋ฆฌํดํ๋ค.
๊ฐ์ฒด๋ฅผ spread ์ฐ์ฐ์๋ก ์์ ๋ณต์ฌํด์ state๋ immutableํ๊ฒ ์์ ๋์๋ค. ์๋ณธ ๋ฐ์ดํฐ state๋ ์์ ํ์ง ์์ ์ํ์์ state๋ฅผ ๋ฆฌํดํ๋ฏ๋ก ์ ์๋ํ๋ค.
๊ฒฐ๋ก
Redux toolkit์์ reducers์ ์์ฑํ๋ ํจ์์์ ์๋ณธ state ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ์ด ๋๋ค๋ฉด? return์ ์ฐ๋ฉด ์๋๋ค.
์๋ณธ๋ฐ์ดํฐ๋ฅผ ๋ณ๊ฒฝ๋ ํ๊ณ , ๊ทธ ๋ณํ๋ ๊ฐ์ ๋ฐํํ๋ ๊ฒ์ด๋ฏ๋ก immer ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์๋ฌ์ฒ๋ฆฌ๋ฅผ ํ๋ค.
์๋ณธ ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ์ด ์๋๊ณ ๋ถ๋ณ์ฑ์ ์ ์งํ ์ํ์ผ๋๋ง return๋ถ๋ฅผ ์จ์ ์ ๋ฐ์ดํธ๋ ์ํ๋ฅผ ๋ฐํํ ์ ์๋ค.
Reference
https://redux-toolkit.js.org/usage/immer-reducers
https://ko.redux.js.org/understanding/thinking-in-redux/three-principles
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
'React' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
MSW(Mock Service Worker) mock API ์ด๋ฏธ์ง ํ์ผ (๋ฐ์ด๋๋ฆฌ) ๋ค์ด๋ก๋ (0) | 2023.08.24 |
---|---|
MVVM ๋์์ธ ํจํด์ผ๋ก react todo app ๊ตฌํ (0) | 2023.06.20 |
d3 shape - line ์ฐจํธ ๋ฐ์ดํฐ ์์ ๋ ๋ผ์ธ ๊ทธ๋ํ ์๋ณด์ ํด๊ฒฐ (0) | 2023.06.12 |
typescript + react | form submit e.preventDefault() ์๋์ํจ (0) | 2023.03.04 |
npm install -g ์๋ฌ EACCES (1) | 2023.01.05 |