리덕스 툴킷을 활용해서 만든 장바구니를 zustand로 만들 것이다.
리덕스 툴킷을 타입 세이프티하게 Cart Slice를 만들어서 장바구니 즉, 음반 바구니를 만들어 봤었다.
리덕스 툴킷 같은 경우는 리덕스만 활용했을 때보다는 개념 자체가 훨씬 간단하고 자체적으로 immer라는 내장 라이브러리가 들어가 있어서 불변성을 굳이 매핑 한다거나 따로 처리를 해줘야 할 필요가 없었다. (스프레드 연산자를 활요해서 이전 상태를 항상 유지해주는 불변성을 유지하는 작업 불필요.) 그래도 보일러 플레이트가 있다.
보일러 플레이트 예를 들어 중앙 저장소, 액션에 대한 타입들도 훅으로 따로 추출, slice 등을 만들었는데 더욱 큰 프로젝트를 하면 더욱 복잡해질 것이다. 그렇기 때문에 zustand로 처리를 해볼 것이다.
zustand와 리덕스 툴킷 비교
zustand 다운로드
$ pnpm add zustand
zustand 관련 store 만들기
리덕스처럼 액션하고 state를 나눠서 관리를 할 것이다.
cart에 맞춰서 수정을 해줘도 되는데 zustand로 리팩토링하는 과정을 겪는 것이기 때문에 기존 내용을 참고해서 수정할 것이다. (like 카드 액션)
immer를 사용한 store를 만들어줄 것이다.
immer라는 라이브러리는 불변성을 유지할 때 활용하는 것이다.
immer의 핵심 함수는 기본 스테이트랑 변경 로직인 프로듀스를 인자로 받는데 프로듀스의 두 번째 인자로 전달되는 콜백이 draft. draft는 실제 원본을 변경하지 않는 상태로 제공이 되면서 마음껏 추가 삭제 변경을 하면, 자체적으로 불변 상태를 만들어 준다.
그리고 store에서는 초기 상태를 다 넘겨줘야 하기 때문에 그것을 넘겨주지 않으면 문제가 발생한다.
여기서 사용한 set은 드래프트를 할 수 있는 수단이라고 생각하면 된다. immer 라이브러리에 함께 쓸 경우. 자체적으로 set만 써도 불변성을 유지한다. immer가 없으면 불변성을 따로 추가적으로 작성해서 유지시켜 줘야 한다.
아래 같은 방식으로 내가 원하는 것을 찾아서 기존 상태를 유지(불변성)하면 되는데,
increase: (id: string) => {
set((state) => ({
cartItems: state.cartItems.map((item) =>
item.id === id ? { ...item, amount: item.amount + 1 } : item
),
}));
},
이머를 사용을 한다면, 이렇게 해줄 필요가 없다. 그냥 아이템을 찾기만 하면 된다. 이렇게 하면 코드가 훨씬 깔끔해진 것을 알 수 있다.
set((state) => {
const cartItems = state.cartItems.find((item) =>
item.id ===
id);
if (cartItems) {
cartItems.amount += 1;
}
});
이런 식으로 actions를 전반적으로 적용해주면 된다.
export const useCartStore = create<CartState>()(immer((set, _) => ({
cartItems: cartItems,
amount: 0,
total: 0,
actions: {
increase: (id: string) => {
// set((state) => ({
// cartItems: state.cartItems.map((item) =>
// item.id === id ? { ...item, amount: item.amount + 1 } : item
// ),
// }));
set((state) => {
const cartItems = state.cartItems.find((item) =>
item.id ===
id);
if (cartItems) {
cartItems.amount += 1;
}
});
},
decrease: (id: string) => {
set((state) => {
const cartItems = state.cartItems.find((item) =>
item.id ===
id);
if (cartItems && cartItems.amount > 0) {
cartItems.amount -= 1;
}
});
},
removeItem: (id: string) => {
set((state) => {
state.cartItems = state.cartItems.filter((item) => item.id
!== id); // 필터를 적용하여 내가 클릭한 것 이외에 나머지 것들을 제거해 준다.
});
},
clearCart: () => {
set((state) => {
state.cartItems = [];
}); // 빈 배열로 초기화
},
calculateTotal: () => {
set((state) => {
let amount = 0;
let total = 0;
state.cartItems.forEach((item) => {
amount += item.amount;
total += item.amount * item.price;
})
state.amount = amount;
state.total = total;
})
},
},
})));
보통 관용적으로 활용하지 않는 것은 언더바(_)를 활용을 한다. 그래서 여기서는 굳이 적어줄 필요가 없어서 _를 적어주면 된다.
-> 아님 확실하게 린팅(문법을 검사하는)을 확실하게 하고 가도 된다.
위의 함수를 직관적으로 쓰기 위해서 단일 구독을 하는 것이 리렌더링 같은 것에 좋고 만약에 여러 개를 한 번에 빼고 싶은 경우에는 shallow를 활용하는 방법도 있다. 여기서 액션은 어차피 객체로 정의했기 때문에 단일 구독이 될 것이다.
export const useCartInfo = () =>
useCartStore(
useShallow((state) => ({
cartItems: state.cartItems,
amount: state.amount,
total: state.total,
}))
);
export const useCartActions = () =>
useCartStore((state) => state.actions
);
얘를 이제 활용만 하면 되는데
Navbar 같은 경우 툴킷에서 썼던 부분은 지우고 아래처럼 작성해주면 된다.
const { amount, cartItems } = useCartInfo();
const { calculateTotals } = useCartActions();
useEffect(() => {
calculateTotals();
}, [cartItems, calculateTotals]);
CartList도 맞게 수정해준다.
const { cartItems } = useCartInfo();
const { clearCart } = useCartActions();
const handleAllClearButton = () => {
clearCart();
};
CartItem 가서도 맞게 수정해주면 된다.
const { increase, decrease, removeItem } = useCartActions();
// 주석 친 것은 redux-toolkit 사용
// const dispatch = useDispatch();
// const handleIncreaseCount = () => {
// dispatch(increase({ id: lp.id }));
// };
const handleIncreaseCount = () => {
increase(lp.id);
};
// const handleDecreaseCount = () => {
// if (lp.amount === 1) {
// dispatch(removeItem({ id: lp.id }));
// return;
// } // 만약에 1이면 아예 빠진다. 밑에까지 가지 않고.
// dispatch(decrease({ id: lp.id }));
// };
const handleDecreaseCount = () => {
if (lp.amount === 1) {
removeItem(lp.id);
return;
}
decrease(lp.id);
};
'UMC 8th Web 워크북' 카테고리의 다른 글
🍎 useCallback, useMemo, memo를 이용한 최적화 (1) | 2025.06.02 |
---|---|
🐻 Zustand 배우기 (0) | 2025.05.30 |
🔧Redux-Toolkit을 활용해서 쇼핑 카트 만들기 (0) | 2025.05.29 |
🍎 useReducer를 왜 쓸까 (0) | 2025.05.29 |
⚽ useDebounce 활용해서 검색하기 (0) | 2025.05.17 |