React + TypeScript + mobX 를 이용한 장바구니 기능 구현
npx create-react-app supermarket --template typescript
npm i mobx-react
src/stores/BasketStore.ts
import { makeObservable, action, observable, computed, toJS } from "mobx";
import { Product } from "./ProductStore";
class BasketClass {
/**
* itemList 를 담아주는 상태값
*/
itemList: Product[] = [];
/**
* 장바구니에 담긴 아이템 가격의 합계를 담아줄 상태값
*/
totalPrice: number = 0;
constructor() {
makeObservable(this, {
itemList: observable,
totalPrice: observable,
updateItem: action,
returnItem: action,
removeItem: action,
setTotalPrice: action,
getItems: computed,
getTotalPrice: computed,
});
}
/**
* 동일한 아이템 선택 시 수량 추가,
* 동일하지 않을 경우 아이템 추가
*/
updateItem(item: Product) {
const found = this.getItems.findIndex((el) => el.id === item.id);
if (found >= 0) this.itemList[found].choice++;
else this.itemList = [...this.itemList, item];
this.setTotalPrice();
}
/**
* 아이템 수량 1개씩 빼기
*/
returnItem(id: number) {
this.itemList = this.itemList.map((item) => {
if (item.id === id && item.choice > 0) item.choice--;
return item;
});
this.setTotalPrice();
}
/**
* 리스트에서 아이템 삭제
*/
removeItem(id: number) {
const idx = this.itemList.findIndex((el) => el.id === id);
this.itemList.splice(idx, 1);
this.setTotalPrice();
}
/**
* 전체 가격 구하기
*/
setTotalPrice() {
this.totalPrice = this.itemList.reduce((acc: number, current: Product) => {
return acc + current.price * current.choice;
}, 0);
}
get getItems() {
return toJS(this.itemList);
}
get getTotalPrice() {
return toJS(this.totalPrice);
}
}
const basketClass = new BasketClass();
export default basketClass;
src/stores/ProductStore.ts
import { makeObservable, action, observable, computed, toJS } from "mobx";
export interface Product {
id: number;
name: string;
price: number;
choice: number;
}
class ProductClass {
productList: Product[] = [
{ id: 0, name: "초코우유", price: 1800, choice: 1 },
{ id: 1, name: "당근", price: 2000, choice: 1 },
{ id: 2, name: "시리얼", price: 8500, choice: 1 },
];
constructor() {
makeObservable(this, {
productList: observable,
removeProduct: action,
});
}
removeProduct(id: number) {
this.productList.splice(id, 1);
}
}
const productClass = new ProductClass();
export default productClass;
src/components/Market.tsx
import "../css/Market.css";
import productStore from "../stores/ProductStore";
import basketStore from "../stores/BasketStore";
import { observer } from "mobx-react";
function Market() {
return (
<div className="Market">
<div className="Market-wrapper">
<div className="products-wrapper">
<h1>판매상품</h1>
<ul>
{productStore.productList.map((product) => (
<li
key={product.id}
onClick={() => basketStore.updateItem(product)}
>
<div className="item-name">{product.name}</div>
<div className="item-price">{product.price}</div>
<button onClick={() => productStore.removeProduct(product.id)}>
X
</button>
</li>
))}
</ul>
</div>
<div className="basket-wrapper">
<h1>장바구니</h1>
<ul>
{basketStore.itemList.map((item) => (
<li key={item.id}>
<div className="item-name">{item.name}</div>
<div className="item-price">{item.price}</div>
<div className="item-choice">{item.choice}</div>
<button onClick={() => basketStore.returnItem(item.id)}>
-
</button>
<button onClick={() => basketStore.removeItem(item.id)}>
X
</button>
</li>
))}
</ul>
<div className="total">total : {basketStore.totalPrice}</div>
</div>
</div>
</div>
);
};
// observer 로 감싸주어야 상태값이 변경될 때 리렌더링 된다
export default observer(Market);