라우팅으로 제품 상세페이지를 구현하려면 다음 3가지 조건이 필요하다.
1] 엔드포인트 :: 선택된 제품에 관한 데이터 경로
2] 데이터 :: 선택된 제품에 관한 데이터
3] 랜더링 :: 프론트에 전달되는 마크업 파일
4] 미들웨어 :: 라우트경로, DB경로, 랜더링 파일을 전달하는 중개자
5] 전송모듈 :: 데이터를 받고, 전달할 수 있는 모듈(ajax, axios, fetch, 기타...)
대부분의 웹서비스는 결국 벡앤드와 프런트를 연결하고, 데이터를 주고받으며 페이지를 생성해내는 역할을 구현한다. 데이터, 디자인의 차이일 뿐, 내부의 구조는 거의 비슷하다. 해당 프로젝트에서 위의 역할을 담당하는 파일들을 정리하면 아래와 같다.
1] 엔드포인트 :: backend\server.js
2] 데이터 :: backend\data.js
3] 랜더링 :: frontend\src\screens\
4] 미들웨어 :: api.js
5] 전송모듈 :: axios npm모듈
1 평점 시스템 구현
1) fontawesome 링크 연결하기
- root\frontend\index.html 상단에 폰트어썸 CDN 삽입
- https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css
2) 컴포넌트 폴더 생성
- root\frontend\src\conponents
3) Rating.js 컴포넌트 파일 생성
- root\frontend\src\components\Rating.js
const Rating = { render: async(props) => {
if(!props.value){
return `<div></div>`
}
return `
<div class="rating">
<span>
<i class="${
props.value >= 1
? 'fa fa-star'
: props.value >= 0.5
? 'fa fa-star-half-o'
: 'fa fa-star-o'
}">
</i>
</span>
<span>
<i class="${
props.value >= 2
? 'fa fa-star'
: props.value >= 1.5
? 'fa fa-star-half-o'
: 'fa fa-star-o'
}">
</i>
</span>
<span>
<i class="${
props.value >= 3
? 'fa fa-star'
: props.value >= 2.5
? 'fa fa-star-half-o'
: 'fa fa-star-o'
}">
</i>
</span>
<span>
<i class="${
props.value >= 4
? 'fa fa-star'
: props.value >= 3.5
? 'fa fa-star-half-o'
: 'fa fa-star-o'
}">
</i>
</span>
<span>
<i class="${
props.value >= 5
? 'fa fa-star'
: props.value >= 4.5
? 'fa fa-star-half-o'
: 'fa fa-star-o'
}">
</i>
</span>
<span>${props.text || ''}</span>
</div>
`
}
}
export default Rating;
|
[ components\Rating.js ]
4) HomeScreen.js 수정하기
- HomeScreen.js가 랜더링될 때, 평점 영역을 새롭게 추가한다.
import axios from 'axios';
import Rating from '../components/Rating.js';
const HomeScreen = {
render: async() => {
const response = await axios({
url: "http://localhost:5000/api/products",
headers:{
'Content-Type': 'application/json',
},
});
if(!response || response.statusText !== 'OK'){ //데이터 응답 실패
return '<div> Error in getting Data </div>'
}
const products = await response.data; //fetch Data 저장
return `
<ul class="products">
${products.map(product =>`
<li>
<div class="product">
<a href="/#/product/${product._id}">
<img src="${product.image}" alt="${product.name}">
</a>
<div class="product-name">
<a href="/#/product/1">
${product.name}
</a>
</div>
// 추가 시작... <div class="product-rating">
${
Rating.render({
value: product.rating,
text: `${product.numReviews} reviews`
})
}
</div>
// 추가 끝... <div class="product-format">
${product.format}
</div>
<div class="product-price">
$ ${ product.price}
</div>
</div>
</li>
` ).join('\n')}
`;
},
}
export default HomeScreen;
|
[ root\frontend\src\screens\HomeScreen.js ]
5) style 추가하기
- 평점(별)관련 css부분 추가하기
/* Rating */ .rating {
color: #ffc000;
font-size: 1.8rem;
}
.rating span:last-child {
color:#444444;
font-size: 1.4rem;
}
.content{
padding: 1rem;
}
|
[ style.css ]
2 제품 EndPoint API추가
1) config.js 생성
- root\frontend\src\config.js
- config.js파일은 외부로 공개할 필요가 없는 정보를 관리한다.
export const apiUrl = 'http://localhost:5000'; |
[ config.js ]
2) api.js 생성
- api.js는 중개자 역할을 한다. 사용자가 요청한 데이터를 직접적으로 받지 않고, 랜더링 파일의 요청에 따라 반환하는 방식으로 관련 데이터를 전달한다.
import {apiUrl} from './config.js';
import axios from 'axios'
export const getProduts = async(id) => {
try{
const response = await axios({
url: `${apiUrl}/api/products/${id}`,
method: 'GET',
headers: {
'Content-Type': 'application/json',
}
});
if(response.statusText !== 'OK'){
console.log('메시지 서버 받기 실패')
throw new Error(response.data.message);
}
return response.data;
}catch(err){
console.log(err);
return { error: err.response.data.message || err.message}
}
}
|
[ root\frontend\src\api.js ]
3) ProductScreen.js 수정하기
- 일단, 엔드포인트의 데이터를 받아오는지 테스트할 목적으로 랜더링 파일을 구현한다.
import {getProduct} from '../api'; import {parseRequestUrl} from '../utils';
import Rating from '../components/Rating';
const ProductScreen = {
render: async() => {
const request = parseRequestUrl();
const product = await getProduct(request.id);
if(product.error){
return `<div> ${product.error} </div>`
}
return `<h1> ${product.name} </h1>`
}
}
export default ProductScreen;
|
[ root\frontend\src\screens\ProductScreen.js ]
4) server.js 수정
- root/backend/server.js
import express from 'express';
import cors from 'cors';
import data from './data.js';
const app = express();
app.use(cors());
app.get('/api/products', (req, res) => {
res.send(data.products);
});
app.get('/api/products/:id', (req, res) => {
const product = data.products.find((x) => x._id === req.params.id);
if(product){
res.send(product);
}else{
res.status(404).send({message: 'product Not Found!'});
}
})
app.listen(5000, () => {
console.log('server at http://localhost:5000');
});
|
[ root\backend\server.js ]
3 제품 상세페이지 구현
1) ProductScreen.js 수정
- 메인페이지의 상품을 랜더링하는 상세페이지를 render()함수 반환값으로 프론트에 전달한다.
import {getProduct} from '../api';
import {parseRequestUrl} from '../utils';
import Rating from '../components/Rating';
const ProductScreen = {
render: async() => {
const request = parseRequestUrl();
const product = await getProduct(request.id);
if(product.error){
return `<div> ${product.error} </div>`
}
// 수정 시작 return `
<div class="content">
<div class="back-to-result">
<a href="/#/"> Back to result </a>
</div>
<div class="details">
<div class="details-image">
<img src="${product.image}" alt="${product.name}"/>
</div>
<div class="details-info">
<ul>
<li>
<h1>${product.name}</h1>
</li>
<li>
${Rating.render({
value: product.rating,
text: `${product.numReviews} reviews`,
})}
</li>
<li>
Price: <strong>${product.price}</strong>
<li>
<li>
Description:
<div>
${product.description}
</div>
</li>
</ul>
</div>
<div class="details-action">
<ul>
<li>
Price : ${product.price}
</li>
<li>
Format :
${
(product.format !== 'fbx')
? `<span class="success"> Other 3D Format </span>`
: `<span class="error"> FBX </span>`
}
</li>
<li>
<button id="add-button" class="fw primary"> Add to Cart </button>
</li>
</ul>
</div>
</div>
</div>
`
// 수정 끝 }
}
export default ProductScreen;
|
[ root\frontend\src\screens\ProductScreen.js ]
2) style.css 수정
- 제품 상세페이지 관련 스타일 추가하기
/* Product Details */ .details{
display:flex;
justify-content: space-between;
align-items: flex-start;
flex-wrap: wrap;
}
.details-image{
flex: 2 1 60rem;
}
.details-image img{
max-width: 60rem;
width: 100%;
}
.details-info,
.details-action {
flex: 1 1 30rem;
}
.details-info ul,
.details-action ul{
padding: 0;
list-style-type: none;
}
.details-info h1{
font-size: 2rem;
margin: 0;
}
.details-info li,
.details-action li{
margin-bottom: 1rem;
}
.details-action{
border: 0.1rem #808080 solid;
border-radius: 0.5rem;
background-color: #f8f8f8;
padding: 1rem;
}
|
[ root\frontend\style.css ]
4 기능 추가하기
1) ProductScreen.js 수정
- 장바구니 버튼에 대한 이벤트 기능을 임시로 구현한다.
import {getProduct} from '../api'; import {parseRequestUrl} from '../utils';
import Rating from '../components/Rating';
const ProductScreen = {
after_render: () => {
// 추가부분 <--
const request = parseRequestUrl();
document.getElementById('add-button').addEventListener('click', () => {
document.location.hash = `/cart/${request.id}`
});
// 추가부분 --> },
render: async() => {
const request = parseRequestUrl();
// 중략...
|
[ root\frontend\src\screens\ProductScreen.js ]
2) index.js 수정하기
- after_render()부분을 추가한다.
import {parseRequestUrl} from './utils.js'; import HomeScreen from './screens/HomeScreen.js';
import ProductScreen from './screens/ProductScreen.js';
import Error404Screen from './screens/Error404Screen.js';
const routes = {
'/' : HomeScreen,
'/product/:id': ProductScreen,
}
const router = async() => {
const request = parseRequestUrl();
const parseUrl = (request.resource ? `/${request.resource}` : '/') +
(request.id ? '/:id' : '' ) +
(request.verb ? `/${request.verb}` : '' );
const screen = routes[parseUrl] ? routes[parseUrl] : Error404Screen;
const mainContainer = document.getElementById('main-container');
mainContainer.innerHTML = await screen.render();
if(screen.after_render) await screen.after_render();
}
window.addEventListener('load', router);
window.addEventListener('hashchange', router);
|
[ root\frontend\src\index.js ]
위의 코드까지 구현한 뒤에 터미널에서 서버(\root>)와 프론트 경로(root\frontend>)에서 npm start를 실행해본다.
* 실행 결과
'웹개발 자료실 > No프레임워크 쇼핑몰제작Code' 카테고리의 다른 글
E-commerce Vanila JS [7편] 몽고DB 연결 (0) | 2021.12.09 |
---|---|
E-commerce Vanila JS [6편] Cart 제작 (0) | 2021.12.08 |
E-commerce Vanila JS [4편] 웹팩 & 바벨 & ESLint 적용 (0) | 2021.12.04 |
E-commerce Vanila JS [3편] 벡앤드 설정 (0) | 2021.12.03 |
E-commerce Vanila JS [2편] 라우팅 기초 (0) | 2021.12.02 |
댓글