[9주차/클레버] 워크북 제출합니다.#88
Conversation
| @@ -0,0 +1,52 @@ | |||
| import type { CartItem } from '../types/cart' | |||
| import { useAppDispatch } from '../store/hooks' | |||
| import { decrease, increase } from '../store/cartSlice' | |||
There was a problem hiding this comment.
전반적으로 Redux 구조가 깔끔하게 잡혀있고, Zustand 전환도 방향이 정확해서 좋았던것같아요!
그런데 removeItem 액션이 UI에 연결되어 있지 않은 것 같아 보입니다
cartSlice.ts에서 removeItem은 잘 정의되어 있는데, CartItemRow.tsx에 이를 호출하는 버튼이 없어서 실제로 dispatch되는 곳이 없는것같아요..! 미션2, 3번에도 마찬가지로 개별 아이템에 삭제 버튼을 다음과 같이 추가해주시면 좋을 것 같습니다
이번주도 수고하셨고, 기말고사 화이팅입니다~!
// 수량 조절 버튼 옆에 추가
<button
type="button"
onClick={() => dispatch(removeItem(item.id))}
className="ml-4 text-red-500 hover:text-red-700"
aria-label={`${item.title} 삭제`}
>
✕
</button>```
| return { cartItems, ...recalculateTotals(cartItems) } | ||
| }), | ||
|
|
||
| decrease: (id) => |
There was a problem hiding this comment.
현재 decrease 함수에서 target 아이템의 수량을 확인하기 위해 먼저 .find()로 배열을 한 번 순회하고 조건에 따라 .filter() 또는 .map()을 돌리며 배열을 두 번 순회하는 구조로 되어 있는 것 같습니다
지금은 데이터가 소수라 체감이 괜찮을 것 같습니다만 향후 대규모 실시간 데이터를 다룰 때 배열을 여러 번 반복 순회 하는 것은 성능 저하의 원인이 될 수 있다고 합니다.
한 번의 순회만으로 "수량 감소"와 "1 이하일 때 삭제" 조건을 동시에 처리할 수 있는 방향을 제안해 드려보았습니당
decrease: (id) =>
set((state) => {
// reduce를 사용하여 한 번의 순회로 map과 filter를 동시에 처리
const cartItems = state.cartItems.reduce((acc, item) => {
if (item.id === id) {
// 수량이 1 이하이면 누적 배열 푸시 하지 않고 건너뜀
if (item.amount <= 1) return acc;
// 1보다 크면 수량을 감소시켜서 푸시
acc.push({ ...item, amount: item.amount - 1 });
} else {
// 상관없는 아이템은 그대로 푸시
acc.push(item);
}
...
| export const { increase, decrease, removeItem, clearCart, calculateTotals } = | ||
| cartSlice.actions | ||
|
|
||
| export default cartSlice.reducer |
There was a problem hiding this comment.
장바구니 전역 상태 관리 구조를 처음부터 아주 탄탄하고 안전하게 잘 잡아주셨네요! 대단하십니다.
특히 아이템 수량이 바뀔 때마다 내부에서 자동으로 총액을 동기화(recalculateTotals)해 주신 덕분에, 컴포넌트단에서 불필요하게 두 번씩 디스패치를 호출할 필요가 없어 유저 인터랙션 제어가 훨씬 깔끔해질 것 같습니다. initialState 선언 직후에 최초 계산을 돌려 초기 진입 시 데이터 싱크를 맞추신 디테일도 정말 좋네요.
현재 구조에서는 모든 액션 내부에서 자동 계산이 완벽하게 돌고 있으므로, 역할이 모호해진 calculateTotals 리듀서와 외부 export 액션만 마저 정리해 주시면 더할 나위 없이 완벽한 코드가 될 것 같습니다!
`import { createSlice, type PayloadAction } from '@reduxjs/toolkit'
import cartItems from '../constants/cartItems'
import type { CartState } from '../types/cart'
function recalculateTotals(state: CartState) {
state.amount = 0
state.total = 0
for (const item of state.cartItems) {
state.amount += item.amount
state.total += item.amount * Number(item.price)
}
}
const initialCartItems = cartItems.map((item) => ({ ...item }))
const initialState: CartState = {
cartItems: initialCartItems,
amount: 0,
total: 0,
}
recalculateTotals(initialState)
const cartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
increase: (state, action: PayloadAction) => {
const item = state.cartItems.find((cartItem) => cartItem.id === action.payload)
if (item) {
item.amount += 1
recalculateTotals(state)
}
},
decrease: (state, action: PayloadAction) => {
const item = state.cartItems.find((cartItem) => cartItem.id === action.payload)
if (!item) return
// 💡 어차피 수량이 1개일 때 마이너스를 누르면 삭제되므로 분기 처리 최적화
if (item.amount <= 1) {
state.cartItems = state.cartItems.filter((cartItem) => cartItem.id !== action.payload)
} else {
item.amount -= 1
}
recalculateTotals(state)
},
removeItem: (state, action: PayloadAction<string>) => {
state.cartItems = state.cartItems.filter((cartItem) => cartItem.id !== action.payload)
recalculateTotals(state)
},
clearCart: (state) => {
state.cartItems = []
state.amount = 0
state.total = 0
},
// 💡 불필요해진 calculateTotals 리듀서 제거
},
})
// 💡 외부 export에서도 굳이 내보내지 않습니다.
export const { increase, decrease, removeItem, clearCart } = cartSlice.actions
export default cartSlice.reducer`
✅ 워크북 체크리스트
✅ 컨벤션 체크리스트
📌 주안점