리액트를 이용한 신발쇼핑몰 개발


이번에 리액트를 학습하면서 만들어본 작은 프로젝트를 소개해드리고자 합니다!

GitHub 레포지토리

Reference

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 }

상품목록 Component화 + 반복문

  1. data.js의 데이터를 import하고 nike라는 state를 만들어 데이터를 모두 집어넣어줬습니다.
  2. Shoes 컴포넌트를 만들어 props를 전송했고, 반복문을 돌려 상품의 갯수만큼 목록을 만들어 return 해주었습니다.
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)

후기

바닐라자바스크립트로 상태관리 프로젝트를 하면서 불편했던 점들이 리액트를 통해 해결되었습니다.
프로젝트를 진행하면서 리액트가 엄청 재미있는 도구라는 걸 느꼈고 다른 프론트엔드 라이브러리인 뷰도 학습하고 싶어졌습니다.
앞으로 더 큰 프로젝트들을 하면서 점점 발전해나가고 싶습니다.

감사합니다.

좋은 웹페이지 즐겨찾기