이번에 리액트를 학습하면서 만들어본 작은 프로젝트를 소개해드리고자 합니다!
https://www.nike.com/kr/ko_kr/
처음엔 기능구현에만 그치려고 했지만, 욕심이 생겨 나이키 홈페이지를 살짝 참고해서 프로젝트에 적용했습니다.☺️
메인 페이지에 보이는 처음 3개의 상품은 data.js에서 저장해둔 데이터를 가져와 화면에 출력시켰고,
더보기 버튼을 눌렀을 때 나오는 상품은 axios를 이용해 API 정보를 가져와 화면에 출력시켰습니다.
data.js
const shoes = [ { id: 0, title: '나이키 에어맥스 97', content: '여성 신발 라이프스타일', price: '199,000 원', img: '/assets/shoe00.jpeg', }, { id: 1, title: '조던 델타 2', content: '여성 신발 라이프스타일', price: '159,000 원', img: '/assets/shoe01.jpg', }, { id: 2, title: '나이키 아쿠아 리프트', content: '여성 신발 라이프스타일', price: '139,000 원', img: '/assets/shoe02.jpg', }, ] export { shoes }
import { shoes } from './data.js' ... let [nike, setNike] = useState(shoes) ... <Shoes nike={nike} /> ... function Shoes(props) { const shoeList = props.nike.map((shoe, i) => { return ( <Link className="col-md-4 box" key={i} to={`/detail/${shoe.id}`}> <img src={'http://benevolentroad.github.io/Nike' + shoe.img} alt={shoe.title} width="100%" /> <h4>{shoe.title}</h4> <p>{shoe.content}</p> <em>{shoe.price}</em> </Link> ) }) return shoeList }
더보기 버튼은 axios를 이용했습니다.
axios를 쓰면 json을 object로 알아서 바꿔줍니다.
yarn add axios
import axios from 'axios' ... <button className="btn btn-dark more" onClick={() => { // 로딩중이라는 ui 띄움 setShowSpinner(true) axios .get('https://codingapple1.github.io/shop/data2.json') .then((result) => { // 로딩중이라는 ui 삭제 setShowSpinner(false) let newNikeArray = [...nike] let prevLength = newNikeArray.length result.data.forEach((e, i) => { e.img = `/assets/shoe0${prevLength + i}.jpeg` newNikeArray.push(e) }) setNike(newNikeArray) }) .catch(() => { // 로딩중이라는 ui 삭제 setShowSpinner(false) console.log('실패') }) }} 더보기 </button>
여러 페이지를 만들기 위해 라우터를 사용했습니다.
yarn add react-router-dom
import { HashRouter } from 'react-router-dom' ... ReactDOM.render( <React.StrictMode> <HashRouter> <App /> </HashRouter> </React.StrictMode>, document.getElementById('root') )
BrowserRouter vs HashRouter
BrowserRouter은 사이트 방문시 URL 맨 뒤에 /#/이 붙은채로 시작해요
사용하는 이유는 실수로 없는 페이지를 서버에 요청해서 404 에러가 뜨는 것을 방지하는 역할을 합니다.
파일에 가서 다음을 import 합니다.
import { Link, Route, Switch } from 'react-router-dom';
Route태그 안에 path와 path 방문시 보여줄 HTML 을 적었습니다.
<Switch> <Route exact path="/"> ... </Route> <Route path="/detail/:id"> <Detail nike={nike} inventory={inventory} order={order} /> </Route> </Switch>
useEffect를 이용해 2초 후에 재고 알림을 사라지게 했습니다.
useEffect(() => { let alertTimer = setTimeout(() => { setShowAlert(false) }, 2000) return () => clearTimeout(alertTimer) }, []) ... ... {showAlert ? ( <div className="my-alert">재고가 얼마 남지 않았습니다.</div> ) : null}
useParams() 라는 훅을 사용하였습니다.
useHistory() 라는 훅을 사용하였습니다.
import { useHistory, useParams } from 'react-router-dom' ... ... let { id } = useParams() let history = useHistory() ... <div className="col-md-6"> <img src={'http://benevolentroad.github.io/Nike' + nike[id].img} alt={nike[id].title} width="100%" /> </div> <div className="col-md-6 mt-4"> <h4 className="pt-5">{nike[id].title}</h4> <p>{nike[id].content}</p> <p>{nike[id].price}</p> {inventory[id] ? <Inventory id={id} /> : null} <button className="btn btn-outline-dark" onClick={() => { history.goBack() }}> 뒤로가기 </button> <button> 주문하기 </button> </div>
let [order, setOrder] = useState(props.order) let [inventory, setInventory] = useState(props.inventory)
주문하기 버튼을 클릭했을 시 해당 상품의 id 값을 order에 저장하고, useEffect를 이용해 재고를 하나 감소시켰습니다.
useEffect(() => { let newInventoryArray = [...inventory] newInventoryArray[order]-- setInventory(newInventoryArray) }, [order]) ... ... <button className="btn btn-dark" onClick={() => { setOrder(id) }}
탭기능은 react bootstrap에 있는 tab UI를 사용했습니다.
https://react-bootstrap.netlify.app/components/navs/#base-nav
탭을 클릭할 때 마다 어떤 버튼을 눌렀는지 state로 저장해 두고 state에 따라 해당 UI를 보이게 하였습니다.
<Nav className="mt-5" variant="tabs" defaultActiveKey="link-0"> <Nav.Item> <Nav.Link eventKey="link-0" onClick={() => { setAnimationSwitch(false) setTab(0) }} 제품설명 </Nav.Link> </Nav.Item> <Nav.Item> <Nav.Link eventKey="link-1" onClick={() => { setAnimationSwitch(false) setTab(1) }} 상세보기 </Nav.Link> </Nav.Item> </Nav> <CSSTransition in={animationSwitch} classNames="tabUI" timeout={500}> <TabContent tab={tab} setAnimationSwitch={setAnimationSwitch} /> </CSSTransition> ... ... function TabContent(props) { useEffect(() => { props.setAnimationSwitch(true) }) if (props.tab === 0) return <div className="mt-5">제품설명 탭 입니다.</div> else if (props.tab === 1) return <div className="mt-5">상세보기 탭 입니다.</div> }
className을 변화시켜 그때마다 CSS를 변경시키는 것도 좋지만, 더욱 간편한 라이브러리가 있어 사용해보았습니다.
react-transition-group을 이용해 animation 이벤트 구현하기
장바구니 데이터는 redux 이용해 구현해보았습니다.
react-redux 사용하기
수령은 dispatch()라는 함수를 써서 데이터 수정 요청을 하였습니다.
<button onClick={() => { props.dispatch({ type: 'increase' }) }} + </button> <button onClick={() => { props.dispatch({ type: 'decrease' }) }} - </button>
index.js
let initialState = [ { id: 0, name: '에어맥스 2021', quan: 2 }, { id: 1, name: '와플 트레이너 2', quan: 1 }, { id: 2, name: '에어맥스 97', quan: 1 }, { id: 3, name: '에어 줌 페가수스 38 플라이이즈', quan: 1 }, { id: 4, name: '메트콘 7 X', quan: 1 }, { id: 5, name: '줌 프릭 3', quan: 1 }, ] function reducer(state = initialState, action) { if (action.type === 'increase') { let copy = [...state] copy[0].quan++ return copy } else if (action.type === 'decrease') { let copy = [...state] if (copy[0].quan > 0) { copy[0].quan-- } return copy } else { return state } } let store = createStore(reducer)
바닐라자바스크립트로 상태관리 프로젝트를 하면서 불편했던 점들이 리액트를 통해 해결되었습니다.
프로젝트를 진행하면서 리액트가 엄청 재미있는 도구라는 걸 느꼈고 다른 프론트엔드 라이브러리인 뷰도 학습하고 싶어졌습니다.
앞으로 더 큰 프로젝트들을 하면서 점점 발전해나가고 싶습니다.
감사합니다.