[9주차/동동] 워크북 제출합니다.#90
Conversation
| payload?: string; | ||
| } | ||
|
|
||
| function reducer(state: IState, action: IAction){ |
There was a problem hiding this comment.
useReducer 파트에서 전개 연산자 ...state를 활용해 불변성을 유지해야 하는 이유를 주석으로 정리해 두셔서 핵심이 잘 전달되는 좋은 예제 코드인것 같습니다.
한 가지 첨언 드릴 것이 있다면,
혹시 의도하신 기능인지는 모르겠지만 카드메이커 직무 변경 이후 타이핑한 텍스트가 남아있을 수 있을 것 같습니다 .
dispatch 성공 후 setDepartment('')를 호출해 입력창을 깔끔하게 비워주는 건 어떨까요?
입력된 데이터는 깔끔하게 정돈되어 보다 나은 UX를 제공할 수 있지 않을까요?
const handleApplyChange = () => {
dispatch({ type: 'CHANGE_DEPARTMENT', payload: department });
if (department === '카드메이커') {
setDepartment(''); // 성공 시 입력창 비우기
}
};
// 버튼의 onClick에 연결
<button onClick={handleApplyChange}> 직무 변경하기 </button>| @@ -0,0 +1,48 @@ | |||
| import { useDispatch } from 'react-redux'; | |||
| import { type ICartItem } from '../constants/cartItems'; | |||
| import { increase, decrease } from '../features/cart/cartSlice'; | |||
There was a problem hiding this comment.
코드 잘 읽었습니다 !
Redux 구조 이해가 잘 된 것 같고, useEffect로 calculateTotals를 연동한 방식도 깔끔하게 구현하신 것 같아요 !
그런데 현재 removeItem이 UI에 연결되어 있지 않아 보입니다..!
cartSlice.ts 28번째 줄에 removeItem이 잘 정의되어 있는데, CartItem.tsx에 이를 호출하는 버튼이 없어서 실제로 dispatch되는 곳이 없는 것 같아요ㅜㅜ!
아래처럼 개별 아이템에 삭제 버튼을 추가해주시면 좋을 것 같습니다!
이번주도 수고 많으셨고, 기말고사 기간 이후 10주차 스터디때 봬용~
// CartItem.tsx - import에 removeItem 추가
import { increase, decrease, removeItem } from '../features/cart/cartSlice';
// 수량 조절 버튼 옆에 추가
<button
onClick={() => dispatch(removeItem(item.id))}
className="ml-2 text-red-500 hover:text-red-700 text-sm font-bold"
>
✕
</button>
| }); | ||
|
|
||
| export const { clearCart, removeItem, increase, decrease, calculateTotals } = cartSlice.actions; | ||
| export default cartSlice.reducer; No newline at end of file |
There was a problem hiding this comment.
수량 변경 로직들이 아주 깔끔하게 잘 구현되었네요!
다만 현재 구조를 보면 increase, decrease, removeItem이 일어난 후에 amount와 total 상태가 자동으로 변하지 않아서, 컴포넌트단에서 매번 calculateTotals 액션을 함께 디스패치해줘야 하는 번거로움이 생길 것 같습니다.
또한 초기 상태(initialState)에서 cartItems 데이터는 들어가 있지만 총액은 0으로 시작되어 첫 진입 시 데이터 불일치가 발생할 위험도 보이네요.
리듀서 내부에 공통으로 총액을 계산해 주는 함수를 선언해 두고, 각 액션이 실행될 때마다 자동으로 계산이 완료되도록 갱신 흐름을 묶어두면 컴포넌트단 코드가 훨씬 가벼워지고 안전해질 것 같습니다.
`import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
import cartItems, { type ICartItem } from '../../constants/cartItems';
interface ICartState {
cartItems: ICartItem[];
amount: number;
total: number;
}
// 💡 1. 상태를 받아 자동으로 총 수량과 금액을 계산해주는 순수 헬퍼 함수
const updateTotals = (state: ICartState) => {
let totalAmount = 0;
let totalPrice = 0;
state.cartItems.forEach((item) => {
totalAmount += item.amount;
totalPrice += item.amount * parseInt(item.price, 10);
});
state.amount = totalAmount;
state.total = totalPrice;
};
// 💡 2. 초기 상태 객체를 선언하고, 최초 1회 계산을 돌려 싱크를 맞춥니다.
const initialState: ICartState = {
cartItems: cartItems,
amount: 0,
total: 0,
};
updateTotals(initialState); // 앱 구동 시 초기 데이터 기준 갱신
const cartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
clearCart: (state) => {
state.cartItems = [];
state.amount = 0;
state.total = 0;
},
removeItem: (state, action: PayloadAction) => {
const itemId = action.payload;
state.cartItems = state.cartItems.filter((item) => item.id !== itemId);
updateTotals(state); // 💡 삭제 시 자동 계산
},
increase: (state, action: PayloadAction) => {
const cartItem = state.cartItems.find((item) => item.id === action.payload);
if (cartItem) {
cartItem.amount += 1;
updateTotals(state); // 💡 수량 증가 시 자동 계산
}
},
decrease: (state, action: PayloadAction) => {
const cartItem = state.cartItems.find((item) => item.id === action.payload);
if (cartItem) {
cartItem.amount -= 1;
if (cartItem.amount < 1) {
state.cartItems = state.cartItems.filter((item) => item.id !== action.payload);
}
updateTotals(state); // 💡 수량 감소 시 자동 계산
}
},
// 💡 컴포넌트단에서 호출할 필요가 없어졌으므로 calculateTotals 리듀서는 완전히 제거합니다.
},
});
// 외부로 export하는 액션에서도 calculateTotals를 제외합니다.
export const { clearCart, removeItem, increase, decrease } = cartSlice.actions;
export default cartSlice.reducer;`
✅ 워크북 체크리스트
✅ 컨벤션 체크리스트
📌 주안점