앞 세션들에서 계속 사용했던 products에 수량을 의미하는 quantity를 추가하고 몇가지 작업을 해봤다.
총 수량과 총 금액을 구하고 싶으면 코드를 어떻게 작성해야할까?
const products = [
{ name: "반팔티", price: 15000, quantity: 1 },
{ name: "긴팔티", price: 20000, quantity: 2 },
{ name: "핸드폰케이스", price: 15000, quantity: 3 },
{ name: "후드티", price: 30000, quantity: 4 },
{ name: "바지", price: 25000, quantity: 5 },
];
//총 수량
const total_quantity = pipe(
map((p) => p.quantity),
reduce((a, b) => a + b)
);
//총 금액
const total_price = pipe(
map((p) => p.price * p.quantity),
reduce((a, b) => a + b)
);
products를 받아서 go함수에 products를 넣고 시작해도 되지만, 이처럼 세션4에서 배웠던 pipe함수로 더 보기 쉽게 바꿀 수 있다.
여기까지의 코드는 아직 좋은 코드라고 보기 어렵다.
한눈에 보기에도 중복되는 부분이 많기 때문이다.
조금씩 더 좋은 코드로 수정해보자.
우선 각 함수의 reduce 안을 살펴보면 모두 같은 작업을 하고 있는 것을 볼 수 있다.
이걸 하나의 함수(add)로 따로 빼주자.
조금 더 넓게 보면, reduce 안 뿐만 아니라 전체적인 구성이 같다.
무엇을 더하는지만 다를 뿐 total_quantity와 total_price 모두 한 요소를 mapping하고 mapping한걸 reduce로 더하고 있기 때문이다.
그래서 어떤걸 집어 넣어도 총 합을 알 수 있게 sum이라는 함수를 만들어 추상화 레벨을 높였다.
const add = (a, b) => a + b;
const sum = (f, iter) => go(iter, map(f), reduce(add));
//총수량
const total_quantity = (products) => sum((p) => p.quantity, products);
//총금액
const total_price = (products) => sum((p) => p.price * p.quantity, products);
조금 더 수정할 수 있는 지점이 보이지 않는가?
앞서 살펴봤던 currying이라는 개념을 적용해보자.
const add = (a, b) => a + b;
const sum = curry((f, iter) => go(iter, map(f), reduce(add)));
//총수량
const total_quantity = sum((p) => p.quantity);
//총금액
const total_price = sum((p) => p.price * p.quantity);
currying한 sum함수는 추상화 레벨이 높은 함수이므로 products뿐만 아니라 다양하게 사용 가능하다.
//ex) 유저의 나이를 나타내는 정보
log(sum((u) => u.age, [{ age: 10 }, { age: 20 }, { age: 30 }]));
이제 본격적으로 장바구니 예제를 알아볼건데, 여기에 사용되는 개념의 링크를 첨부하겠다.
아래 코드를 따라가다가 헷갈리는 부분이 나올 때 다시 올라와서 개념을 확인해보자.
[HTML] 테이블(Tables)
데이터를 행(row)과 열(column)로 이루어진 테이블 형식으로 만들고 싶을 때 사용한다. table 정의 모든 table 생성의 시작은 태그이다. table의 한 셀(cell) 정의 다른 태그 없이 태그만 사용하면 같은 행
r-ing.tistory.com
[JavaScript] 템플릿 리터럴 (Template Literals)
Template Literals ES6에서 새롭게 도입된 문자열 표기법 back-tick(`) 문자를 사용한다. 기존에 문자열을 표기할 때 작은 따옴표(')나 큰 따옴표(")를 사용했는데, ES6부터는 백틱(`) 문자를 사용해 더 편리
r-ing.tistory.com
최종 만들게 될 화면은 다음과 같다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>HTML 출력해보기 -장바구니</title>
<script src="fx.js"></script>
</head>
<body>
<div id="cart"></div>
<script>
const products = [
{ name: "반팔티", price: 15000, quantity: 1 },
{ name: "긴팔티", price: 20000, quantity: 2 },
{ name: "핸드폰케이스", price: 15000, quantity: 3 },
{ name: "후드티", price: 30000, quantity: 4 },
{ name: "바지", price: 25000, quantity: 5 },
];
const add = (a, b) => a + b;
const sum = curry((f, iter) => go(iter, map(f), reduce(add)));
//총수량
const total_quantity = sum((p) => p.quantity);
//총금액
const total_price = sum((p) => p.price * p.quantity);
document.querySelector("#cart").innerHTML = `
<table>
<tr>
<th>상품 이름</th>
<th>가격</th>
<th>수량</th>
<th>총 가격</th>
</tr>
${go(
products,
map(
(p) => `
<tr>
<td>${p.name}</td>
<td>${p.price}</td>
<td><input type="number" value="${p.quantity}"/></td>
<td>${p.price * p.quantity}</td>
</tr>
`
),
reduce(add)
)}
<tr>
<td colspan="2">합계</td>
<td>${total_quantity(products)}</td>
<td>${total_price(products)}</td>
</tr>
</table>
`;
</script>
</body>
</html>
50번째 줄) map함수는 array를 return하므로 reduce 전에는 각 문자열이 '배열'에 들어가있는 상태이다.
reduce(add)를 해서 하나의 문자열로 만들면 정상출력된다.
위에서 total_quantity, total_price를 만들 때 썼던 sum함수에서 iterable을 받고 map(f), reduce(add) 를 했다.
sum이 가격, 유저의 나이 등을 더할 수 있다는 뜻은 f안에 들어오는 무엇이든 축약 수 있다는거다.
여기까지 생각해보고 위 코드의 40~51번 라인을 보면 익숙한 형식이 보이지 않는가?
위 코드의 40~51부분을 다음과 같이 sum함수로 수정할 수 있겠다.
${go(
products,
sum(
(p) => `
<tr>
<td>${p.name}</td>
<td>${p.price}</td>
<td><input type="number" value="${p.quantity}"/></td>
<td>${p.price * p.quantity}</td>
</tr>
`
)
)}
선택되었는지 안되어있는지를 확인하는 부분을 추가시켜 장바구니 예제를 조금 더 발전시켜본 후 마무리하겠다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>HTML 출력해보기 -장바구니</title>
<script src="fx.js"></script>
</head>
<h1>장바구니 예제</h1>
<body>
<div id="cart"></div>
<script>
const products = [
{ name: "반팔티", price: 15000, quantity: 1, is_selected: true },
{ name: "긴팔티", price: 20000, quantity: 2, is_selected: false },
{ name: "핸드폰케이스", price: 15000, quantity: 3, is_selected: true },
{ name: "후드티", price: 30000, quantity: 4, is_selected: false },
{ name: "바지", price: 25000, quantity: 5, is_selected: false },
];
const add = (a, b) => a + b;
const sum = curry((f, iter) => go(iter, map(f), reduce(add)));
//총수량
const total_quantity = sum((p) => p.quantity);
//총금액
const total_price = sum((p) => p.price * p.quantity);
document.querySelector("#cart").innerHTML = `
<table>
<tr>
<th>상품 이름</th>
<th>가격</th>
<th>수량</th>
<th>총 가격</th>
</tr>
${go(
products,
sum(
(p) => `
<tr>
<td><input type="checkbox" ${
p.is_selected ? "checked" : ""
}/></td>
<td>${p.name}</td>
<td>${p.price}</td>
<td><input type="number" value="${p.quantity}"/></td>
<td>${p.price * p.quantity}</td>
</tr>
`
)
)}
<tr>
<td colspan="2">합계</td>
<td>${total_quantity(filter((p) => p.is_selected, products))}</td>
<td>${total_price(filter((p) => p.is_selected, products))}</td>
</tr>
</table>
`;
</script>
</body>
</html>
products에 is_selected 요소를 추가했고, 마지막 total에서 is_selected가 true인 상품 (선택된 상품)만 더해야하므로 filter로 걸러주었다.