17. createSlice - 간결한 리듀서 작성
16편에서 createSlice가 무엇을 대신해주는지 미리 봤습니다. 이 편은 createSlice의 세 필드(name, initialState, reducers)를 하나씩 뜯어보고, 15편에서 순수 Redux로 작성한 counterSlice와 todosSlice를 실제로 다시 작성합니다.
학습 목표
createSlice의name,initialState,reducers필드의 역할을 설명할 수 있다.reducers에 정의한 함수 이름으로부터 액션 타입과 액션 생성자가 어떻게 만들어지는지 설명할 수 있다.- 페이로드가 여러 값으로 구성될 때
prepare콜백을 사용할 수 있다.
createSlice의 세 필드
| |
name: "counter":reducers의 각 키(incremented)와 결합해"counter/incremented"라는 액션 타입 문자열을 만든다.initialState: 07편에서 리듀서 함수의 기본 매개변수로 썼던 것과 동일하다.reducers: 각 함수가 07편의switch문 케이스 하나에 대응한다. 함수 이름이 곧 액션 타입의 나머지 부분이 된다.
흔한 오개념 하나: reducers의 키(incremented)는 단순한 함수 이름이 아니라, name과 결합해 실제로 dispatch되는 액션 타입 문자열의 일부가 됩니다. 즉 incremented를 handleIncrement로 리네이밍하면, 액션 타입도 "counter/incremented"에서 "counter/handleIncrement"로 바뀝니다. DevTools 로그에 남은 과거 기록이나, 이 액션 타입 문자열을 직접 참조하는 다른 코드(예: 다른 slice의 extraReducers)가 있다면 이 리네이밍은 겉보기와 달리 브레이킹 체인지입니다.
createSlice가 반환하는 객체는 { name, reducer, actions, caseReducers }를 갖습니다. 이 중 slice.reducer(합쳐진 리듀서 함수)와 slice.actions(액션 생성자 모음)를 주로 씁니다.
| |
payload가 있는 액션: action.payload
인자를 받는 액션 생성자는 두 번째 매개변수 action으로 페이로드에 접근합니다.
| |
incrementedBy(5)를 호출하면 createSlice가 자동으로 { type: "counter/incrementedBy", payload: 5 } 형태의 액션 객체를 만들어줍니다. 07편에서 손으로 작성했던 (amount) => ({ type: "counter/incrementedBy", payload: amount })와 동일한 결과입니다.
15편의 counterSlice를 createSlice로 다시 쓰기
| |
15편의 순수 Redux 버전(액션 생성자 3개 + switch 문 4케이스, 총 22줄)과 비교하면 이 버전은 14줄입니다. 무엇보다 "counter/incremented" 같은 문자열을 어디에도 직접 쓰지 않았다는 점이 핵심입니다.
15편의 todosSlice를 createSlice로 다시 쓰기
배열 상태를 다루는 todosSlice도 다시 써봅니다. 08편에서 배운 불변 추가/삭제([...state, item], filter())가 createSlice 안에서는 어떻게 바뀌는지 주목하세요.
| |
15편에서 [...state, item]으로 불변 추가를 했던 것이, RTK 버전에서는 state.push(action.payload)로 직접 변경처럼 보입니다. 이것이 가능한 이유는 createSlice의 reducers 함수 내부에서 state가 실제 상태가 아니라 Immer의 draft 객체이기 때문입니다. draft에 가한 변경은 Immer가 추적해, 실제로는 새 불변 상태를 만들어 반환합니다. todoRemoved처럼 return으로 새 배열을 명시적으로 반환하는 것도 여전히 유효합니다.
prepare 콜백: 페이로드 조립 로직 분리하기
todoAdded에서 쓴 { reducer, prepare } 형태에 주목하세요. 액션 생성자가 받는 인자(text)와 실제 페이로드 형태({ id, text, done })가 다를 때, prepare 콜백으로 이 변환 로직을 리듀서와 분리합니다.
| |
nanoid()는 RTK가 내장 제공하는 고유 ID 생성 함수로, 15편에서 임시로 썼던 Date.now()보다 충돌 위험이 낮습니다. prepare 없이 reducer만 쓸 때는 액션 생성자가 받은 인자가 그대로 action.payload가 되지만, prepare를 쓰면 인자를 원하는 형태로 가공한 뒤 payload(그리고 필요하면 meta, error)로 반환할 수 있습니다.
extraReducers: 다른 slice의 액션에 반응하기
가끔 한 slice의 리듀서가 자신이 정의하지 않은 액션 타입에 반응해야 할 때가 있습니다. 예를 들어 “전체 초기화” 액션이 여러 slice에 동시에 영향을 줄 때입니다.
| |
extraReducers는 19편에서 다룰 createAsyncThunk가 자동 생성하는 pending/fulfilled/rejected 액션을 처리할 때 가장 자주 쓰입니다. 지금은 “slice 밖에서 정의된 액션에도 반응할 수 있다"는 것만 기억해두면 충분합니다.
builder는 addCase 외에도 두 가지 메서드를 더 제공합니다. **addMatcher**는 정확한 액션 타입이 아니라 조건 함수로 여러 액션에 한 번에 반응할 때 씁니다.
| |
여러 개의 createAsyncThunk가 공통으로 “로딩 시작” 처리를 해야 할 때, addCase를 각 thunk마다 반복해서 쓰는 대신 addMatcher + isAnyOf로 한 번에 묶을 수 있습니다. **addDefaultCase**는 어떤 addCase/addMatcher에도 걸리지 않은 액션에 대한 기본 동작을 정의합니다(자주 쓰이지는 않지만, 알아두면 “매칭되지 않은 나머지"를 명시적으로 다룰 수 있습니다).
실무 체크리스트
reducers의 함수 이름이 액션 타입의 의미를 명확히 드러내는가(incremented가 아니라handleClick같은 모호한 이름은 피한다)?- 페이로드를 조립하는 로직이 복잡하다면
prepare콜백으로 분리했는가? extraReducers가 필요한 경우는 “이 slice 밖에서 정의된 액션에 반응해야 할 때"로 한정하고 있는가?
연습 과제
기초(★☆☆)
- 15편의
todosReducer에서todoToggled,todoRemoved케이스를createSlice의reducers로 옮겨보세요(이미 위 예시에 있지만, 직접 타이핑하며 익혀보세요).
중급(★★☆)
prepare콜백을 사용해,todoAdded가priority("high"/"normal"/"low") 인자도 받아 페이로드에 포함하도록 확장해보세요.
고급(★★★)
extraReducers로counterSlice가allDataReset액션에 반응해count를 0으로 되돌리도록 구현해보세요.- 두 개의 서로 다른
createAsyncThunk를 만들고,addMatcher+isAnyOf로 두 thunk의pending상태를 하나의 매처에서 함께 처리해보세요.
요약
createSlice는name+reducers의 키를 조합해 액션 타입을,reducers의 값으로 리듀서 케이스와 액션 생성자를 자동 생성한다.reducers함수 안의state는 Immer draft이므로 직접 변경 스타일과return스타일 모두 쓸 수 있다.- 페이로드 조립이 복잡하면
prepare콜백으로, slice 밖 액션에 반응해야 하면extraReducers로 처리한다.
참고 문헌 및 출처(추천)
- Redux Toolkit 공식 문서, “createSlice” API 레퍼런스
- Redux Toolkit 공식 문서, “Usage With TypeScript, reducers.prepare”
- Immer 공식 문서, “Update patterns”
![Featured image of post [Redux] 17. createSlice - 간결한 리듀서 작성](/post/redux/create-slice/wordcloud_hu_469f6b11ed496c19.webp)
![[Redux] 15. 실습: Counter와 Todo 앱 만들기](/post/redux/practice-counter-todo/wordcloud_hu_5c499868ea4452c8.webp)
![[Redux] 16. Redux Toolkit 소개 - 왜 RTK인가?](/post/redux/redux-toolkit-introduction/wordcloud_hu_c57040366cbc5d2c.webp)
![[Redux] 17. createSlice - 간결한 리듀서 작성](/post/redux/create-slice/wordcloud_hu_835b90d6ba21615f.webp)
![[Redux] 18. configureStore - Store 설정 자동화](/post/redux/configure-store/wordcloud_hu_286e52baac5619ca.webp)
![[Redux] 19. createAsyncThunk - 비동기 로직 단순화](/post/redux/create-async-thunk/wordcloud_hu_37ab180eb6db5e9b.webp)
![[Redux] 20. 실습: RTK로 앱 리팩터링](/post/redux/practice-rtk-app/wordcloud_hu_97bfaa8516f2360e.webp)
![[Redux] 24. RTK Query - 데이터 페칭의 혁명](/post/redux/rtk-query/wordcloud_hu_1807a958ecd17a19.webp)