1차 프로젝트는 thisisneverthat 의 클론 코딩이다.
나는 크게
Back-end : Cart API, middlewares(validateToken)
를 맡았고, 대략적으로 전체적인 style 정리를 하였다.
첫날 DB modeling 을 할땐, 과연 이게 맞는 것인가..? 라는 의문이 들면서 참여 했던것 같다.
물론 다 끝난 지금은 더 디테일 했으면 좋겠는데~ 라는 생각이 들지만, 시간상 부족했던 것과, 단 기간에 충분히 뽑아낼 수 있는 것을 한것 같다.
나는 아직 Back 과 Front 에서 정하지 않았다.
욕심인지는 알지만, 열심히 해서 두개를 같이 병행했으면 한다.
처음 무엇을 정해야할지 모르기에 우선 팀원들과 같이 명세서로 정했지만, 프론트가 금방 끝나버렸다.
그 이유는 이렇다.
나는 퍼블리셔 출신이기에 css 를 어느정도 잘 잡는다고 생각한다.(어느 정도 퍼블리셔로 활동했기 때문이다.)
그래서 회원가입과 로그인 Nav, Footer, Search bar 관련해서 dom 을 구성하는 것과 scss를 짜는건 금방하였다.
그 결과...
시간이 너무 남았고, backend를 더 하고 싶은 열망으로 백을 더 하고 싶고, 할게 더 필요하다 라고 팀원들에게 요청을 하였다. 팀원들은 고맙게도 그것을 받아들여줬고, CART API 관련해서 맡게되었다.
그치만 난 내가 프론트를 다 하지 않았다는걸 뒤늦게 알게된다.
그건 바로 회원가입, 로그인 의 토큰 작업이다.
단지 간단하게, localStorage
에 저장해서 그것에 대한 token 을 어디든 뿌려주어야겠네'라고 생각했다.
하지만..
uselocation
을 사용하고 useEffect
, useState
를 써봤지만, 아무리해도 새로고침을 해야지만 useState
가 반영이 되어 token 이 저장이 되었다. 꽤나 많이 버벅 된것 같다.
우여곡절 끝에 알아냈던 것이 useContext
이다.
간단히 말하면 전역적으로 데이터를 공유할수 있도록 공유하는 방법이다. 매번 트리 단계마다 명시적으로 props를 넘겨주지 않아도 많은 컴포넌트가 값을 공유 할 수 있다.
createContext
로 context인 userContext
를 만들어주고,useState
을 사용하여 token을 가져와서 저장해주도록 해주었다.value
값으로 token, setToken
을 넘겨주었다.{props.children}
으로 받을 children 을 넣어준다. export const UserContext = createContext(null);
function UserStore(props) {
const [token, setToken] = useState(() => {
const saved = localStorage.getItem('token');
return saved || '';
});
return (
<UserContext.Provider
value={{
token,
setToken,
}}
>
{props.children}
</UserContext.Provider>
);
}
function Router() {
return (
<BrowserRouter>
<UserStore>
<Header />
<Routes>
<Route path="/" element={<Home />} />
</Routes>
<Footer />
</UserStore>
</BrowserRouter>
);
}
import React, { useState, useContext, useEffect } from 'react';
import { UserContext } from '../../store/UserStore';
function Header() {
const { token, setToken, cartStatus } = useContext(UserContext);
굉장히 센세이션 하였다. 그 이후로 Context ApI
는 자주 사용하게 된 것 같다.
단, context 를 사용하면 컴포넌트를 재사용하기 힘들기 때문에 사용 전 꼭 잘 생각하도록 하자.
추후에 redux 를 사용하는 법도 공부해봐야겠다.
기능적으로는 구현이 안되었지만, Search bar는 header 에 붙어있는 모달창이다.
다행히 수업시간에 배운것이 있기 때문에, portal modal
을 사용하였다.
portal modal
을 사용하면 기존 modal 을 사용할때보다 부모의 제약을 벗어나서 간단하게 재사용 가능하다.
기존 modal 은 매번 z-index
를 부모보다 높게 주거나하여 보이게 하였지만, portal modal
은 간단하다. 물론 아예 쉽지는 않다.
<div id="root"></div>
<div id="modal"></div>
createPoral
메서드를 사용하여 지정해준다. 이렇게 지정을 해주면, {props.children}
안에 props
로 만들어둔 컴포넌트가 children 으로 들어가게 된다. 이러면 선언은 끝이다! import ReactDOM from 'react-dom';
import css from './modal.module.scss';
const modal = document.getElementById('modal');
function ModalLayout(props) {
return ReactDOM.createPortal(
<>
<div className={css.dim} onClick={props.openModal} />
{props.children}
</>,
modal
);
}
export default ModalLayout;
modalLayout
을 불러주고, 위에 말했듯이 모달로 띄어줄 컴포넌트를 넣어주면 된다. {isShowing && (
<ModalLayout openModal={openModal}>
<Search openModal={openModal} />
</ModalLayout>
)}
isShowing
은 기본 값인 false
인 useState
값으로 openModal
이라는 함수를 주어 true/false
로 변경하게 하였다.
이렇게 매번 사용할 때마다 <ModalLayout>
을 호출하여 만들어준 컴포넌트를 넣어주면 된다.
이 방법도 꽤 자주 사용할 것 같다.
위에 언급했듯이, 나는 프론트를 다 끝낸줄 알고 맘이 편하게있었고, 그 결과 굉장히 바빴다.
부랴부랴 CART API
를 한것 같다.
다행히도, 내가 나머지 프론트 작업을 하였을때, 다른 팀원분이 API 를 얼추 만들어주셔서 정말 감사했다.
2주가 시작되고, CART의 프론트 작업본을 가지고 Back 과 연동하기 시작했다.
막막할 따름이다. 어디서부터 어떻게 시작할지 감이 안잡혔다.
너무 front 에만 연연한 기분이다. 정신을 다시 잡고 팀원분들에게 도움을 요청하였다. 어느정도 CART API
가 되었기 때문에, fetch
를 사용하여 연동하였다.
하지만 이때도, token 과 같은 이슈가 발생하였다. 만들어 줬던 useStore에
을 넣어줬고, 그것을 카트에 crud가 변경 될때마다 cartStatus
가 변경되게 만들어주었다.
/* UseStore.js */
const [cartStatus, setCartStatus] = useState(false);
const handleUpdate = cal => {
fetch(`${BASE_URL}/cart/${item.id}`, {
method: 'PUT',
headers: {
Authorization: token,
'Content-Type': 'application/json',
},
body: JSON.stringify({
quantity: item.quantity,
cal: cal,
}),
})
.then(res => res.json())
.then(() => {});
setCartStatus(prev => !prev);
};
-
,+
버튼을 눌렀을때, 자꾸 값이 안보내지는 이슈가 있었다. 왜 안될까 했지만... body에 JSON을 보낼때는, header 에 Content-Type: application/json
을 넣어줘야 하는 것을 깊게 깨달았다.
token을 보낼때는 header
에 Authorizaion
key로 token 을 보내주게 된다.
context API 로 지정한 setCartStatus
값을 이전 값과 반대 값으로 변경한다.
-> 처음엔 false
라는 값을 줬고, 눌렀을ㄷ때 true
값으로 변경 시켜줬는데, 한번 밖에 변경이 안되었다. 그래서 아예 값을 계속 toggle 하는 형식으로 변경 해주었더니, 실시간으로 반영이 잘된다.
수량이 0일때 -
를 누르거나, x
버튼을 눌렀을때 삭제 하는것도 같은 방식으로 적용하였다.
Add to Cart 버튼을 누르면 카트에 추가되는 기능이다.
useEffect(() => {
fetch(`${BASE_URL}/product${location.search}`, {
method: 'GET',
})
.then(res => res.json())
.then(data => {
setProductDetails(data);
let productChangeId = data.data.stockBySize.findIndex(
v => v.id == productColorId
);
setCheckingSize(
data.data.stockBySize[productChangeId].size_stock[0]
.product_details_id
);
});
}, [location]);
location
을 사용하여 url 이 변경되었을때, fetch를 하여, 상품상세정보인 colorId와 product_detail_id 를 불러온다.
그 값을 이용하여 handleAddCart
함수를 만들어주는데,
const handleAddCart = () => {
fetch(`${BASE_URL}/cart`, {
method: 'POST',
headers: {
Authorization: token,
'Content-Type': 'application/json',
},
body: JSON.stringify({
product_details_id: checkingSize,
quantity: 1,
}),
})
.then(res => res.json())
.then(() => {});
setCartStatus(prev => !prev);
};
저 quantity:1
값과 product_details_id
가 중요하다.
같은 상품을 계속 추가했을때, 수량이 추가되는게 아니라 계속 줄 수가 추가 되는 경우가 발생하였다.
이런 현상으로 CART API 를 변경하였다.
services
단에서, addCartItem
을 추가하게되면, product_details_id , user_id
값이 있는지 확인하고, 그 값이 있다면, model
에 새로 추가한 updateQuantity
를 이용하여 단지 quantity 값만 추가 되는 UPDATE
sql 을 추가해줬다.
const checks = await checkUserProductDetail({ user_id, product_details_id });
if (checks.length) {
const currentQuantity = checks[0].quantity + 1;
const cartId = checks[0].id;
await updateQuantity({
cartId,
currentQuantity,
});
} else {
await addItem(addItemDto);
}
}
이 여러 작업을 통하여 꽤나 익숙해진 것 같다. 어떤식으로 값이 넘어가고, 어떻게 확인을 해야하며, 어떻게 처리하는지에 대해 알게되었다. 조금 더 디테일하고, upsert
라는 sql 문법을 사용하게끔 더 공부해야겠다.
이 작업을 하기 전까진 middlewares 라는 것의 감을 못잡고 있었다.
하면서 공부를 많이 하였다. next()
라는 것은 어떻게 쓰는지 던져주는 것이라고 알게되었고,
middlewares는 공통적으로 원하는 값을 체크할수있는 것이라고 생각한다.
const jwt = require('jsonwebtoken');
const validateToken = async (req, res, next) => {
try {
const token = req.header('Authorization');
const user = jwt.verify(token, process.env.SECRET_KEY);
req.userId = user.id;
next();
} catch (err) {
next(err);
}
};
이렇게 header 로 보냈던 Authorization
을 token
으로 받아서 user id로 복호화 시킨다.
이건 index.js
에 전체적으로 cart를 불러와서, 모든 cart api
에 만들어두었던, validateToken
을 거치게 만들어준다.
처음엔 일일히 다 넣어줬지만, 이렇게 한번에 처리하는 형식으로 리펙토링 해주었다.
const cartRouter = require('./cart');
const { validateToken } = require('../middlewares/validateToken');
const router = express.Router();
router.use('/cart', validateToken, cartRouter);
시간이 어떻게 지나갔는지 모를정도로 정말 빨리 지나갔다.
힘들기도 매우 힘들었지만, 새로운것을 계속 알아가는 기쁨은 역시 매우 엄청 즐겁다.
힘들만큼 즐겁지만.. 정말 힘들긴했다.
퍼블리셔였기에 여러 디자이너와, 개발자와 협업을 해왔었지만, 이런 식의 협업은 또 색다른 느낌을 받게된다.
개발자가 아닌, 개발자가 되기 위한 사람들과의 협업은 나를 더 공부의 늪으로 빠지게 만들고, 공부에 대한 열망을 높게 만든다.
공부하는 재미는 즐겁다. 새로운 것을 알아가는 것도 즐겁다.
굉장히 다음 프로젝트도 너무 기대된다.