5월 30일(React, 쇼핑몰 만들기)

JY·2022년 5월 30일
1
post-thumbnail

쇼핑몰 만들기

1. 이미지 넣기


배너 이미지

CSS


CSS 상에서는 다음처럼 배경을 넣을 수 있다.

./이미지경로

// App.js

<div className="main-bg"></div>
// App.css

.main-bg {
  height: 300px;
  background-image: url('./banner.jpg');
  background-size: cover;
  background-position: center;
}

HTML

HTML 상에서는 CSS 방식과는 다르게 import를 해서 필요한 곳에서 변수를 사용할 수 있다.

import 작명 from './이미지경로'

// App.js

import Banner from './banner.jpg'

<div className="main-bg" style={{backgroundImage: 'url('+ Banner +')'}}></div>

👉 결과

public 폴더 이용

HTML 상에서 이미지를 넣는 일은 상당히 번거롭다. 만약, 이미지 100개가 필요하다면 import를 100번 해야 하는 번거로움이 있다.
매번 import 하기 번거롭다면 public 폴더에 이미지를 보관하는 방법도 있다.

/이미지경로

🤔 서브경로에 사이트를 발행 시
/이미지경로 이런 식으로 사용하면 서브경로에 사이트를 발행했을 때 이미지의 모든 경로를 수정해야 할 수 있다. 따라서 다음과 같은 CRA 권장 방식을 사용하는 것이 좋다.

<img src={process.env.PUBLIC_URL + '/이미지'>

상품목록 만들기

Bootstrap grid를 활용하여 상품목록을 만들었다.

<Container>
  <Row>
    <Col>
  	  <img/>
  	  <h4></h4>
	  <p></p>
	  <p></p>
  	</Col>
  </Row>
</Container>

👉 결과

2. import, export

(서버에서 데이터를 가져왔다고 가정하고 데이터를 state로 관리했다.)

코드가 너무 길고 복잡하다면 import와 export를 이용해 파일을 분리할 수 있다.
함수, 컴포넌트도 export가 가능하다.

import / export

내보내려면? export

let a = 10;

export default a

가져오려면? import

import 작명 from './data.js'

여러 개 import / export

내보내려면? export { 변수1, 변수2 } 형식

let a = 10;
let b = 20;

export {a, b}

가져오려면? 작명을 할 수 없고, export한 변수명 그대로 import

import {a, b} from './data.js'

현재 데이터는 let data = [{}, {}, {}] 형식으로 저장되어 있는 것이다.

// data.js

let data = [
  {
    id : 0,
    title : "CALONE 17",
    content : "245 g / 8.6 oz classic candle",
    price : 170000
  },

  {
    id : 1,
    title : "THÉ NOIR 29",
    content : "50 ml / 1.7 fl oz eau de parfum",
    price : 220000
  },

  {
    id : 2,
    title : "SHOWER GEL",
    content : "250 ml / 8.5 fl oz hinoki",
    price : 110000
  }
]

export default data
// App.js

import data from './data'

let [products] = useState(data);

+) 상품목록 컴포넌트로 만들기, map()

다음처럼 props로 전달해서 상품목록을 출력할 수도 있다. 하지만 이는 우리가 data가 3개 있다는 사실을 알고 있으므로 가능한 것이다.
만약, 데이터가 100개라면 컴포넌트를 100번 호출하는 것은 좋지 않은 방식이므로 map을 이용해보자.

// App.js

<div className="App">
  <Card products={products[0]} i={1} />
  <Card products={products[1]} i={2} />
  <Card products={products[2]} i={3} />
</div>
  
...

function Card(props) {
  return (
    <>
      <Col>
        <img src={`/${props.i}.jpg`} />
        <h4>{props.products.title}</h4>
        <p>{props.products.content}</p>
        <p>{props.products.price}</p>
      </Col>
    </>
  )
}
// App.js

<div className="App">
  ...
  <Container>
    <Row>
      {products.map((item, i) => <Card products={item} i={i+1} key={item.id} />)}
    </Row>
  </Container>
</div>

👉 결과
하드코딩으로 채워넣었을 때랑 똑같이 출력되는 것을 볼 수 있다.

3. Router


/detail: 상세 페이지를 보여주고,
/cart: 장바구니 페이지를 보여주도록 만들어 보자.

  • 리액트 미사용 시

    • HTML 파일 만들어서 상세페이지 내용을 채운다.
    • /detail로 접속하면 HTML 파일 보내준다.
  • 리액트(SPA)

    • HTML을 하나만 사용(index.html)
    • 컴포넌트 만들어서 상세페이지 내용을 채운다.
    • /detail로 접속하면 그 컴포넌트를 보여준다.

🤔 import 시 경로

  • 내가 만든 js 파일들은 ./부터 시작한다.
  • 경로가 없는 것은 대부분 설치한 라이브러리이다.

Router를 이용한 메인페이지, 상세페이지 나누기

페이지 하나를 컴포넌트로 만들어 두는 것이 좋다.

// index.js

root.render(
  <Provider store={store}>
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
  </Provider>
);
// App.js

function App() {
  let [products] = useState(data);

  return (
    <div className="App">
      <Navbar bg="dark" variant="dark">
        <Container>
        <Navbar.Brand href="#home">LE LABO</Navbar.Brand>
        <Nav className="me-auto">
          <Nav.Link href="#home">Home</Nav.Link>
          <Nav.Link href="#features">Cart</Nav.Link>
        </Nav>
        </Container>
      </Navbar>

      <Routes>
        <Route path="/" element={
          <>
            <div className="main-bg" style={{backgroundImage: 'url('+ Banner +')'}}></div>
            <br/>
            <Container>
              <Row>
                {products.map((item, i) => <Card products={item} i={i+1} key={item.id} />)}
              </Row>
            </Container>
          </>
      } />
        <Route path="/detail" element={<div>상세페이지</div>} />
      </Routes>
      {/* <Cart /> */}
    </div>
  );
}

👉 메인페이지

👉 상세페이지

버튼으로 페이지 이동

// App.js

<Link to="/"></Link>
<br/>
<Link to="/detail">상세페이지</Link>

+) 상세페이지 채우기

// App.js

import Detail from './Detail';
function App() {
  ...
  return (
    ...
	<Link to="/detail">상세페이지</Link>
    ...
    <Routes>
      <Route path="/" element={...} />
      <Route path="/detail" element={<Detail />} />
    </Routes>
  );
}
// Detail.js

import React from 'react'

export default function Detail() {
  return (
    <div className="container">
      <div className="row">
        <div className="col-md-6">
          <img src="./1.jpg" width="100%" />
        </div>
        <div className="col-md-6">
          <h4 className="pt-5">상품명</h4>
          <p>상품설명</p>
          <p>120000</p>
          <button className="btn btn-danger">주문하기</button> 
        </div>
      </div>
    </div>
  )
}

👉 결과

useNavigate(): 페이지 이동 도와주는 함수이다. 보통 다음처럼 변수에 담아서 사용한다.

let navigate = useNavigate();

Link 대신 사용할 수 있다.

<Nav.Link><Link to="/">Home</Link></Nav.Link>

// 이렇게
<Nav.Link onClick={() => navigate('/')}>Home</Nav.Link>

👉 결과

🤔 navigate(1), navigate(-1)
앞으로 한 페이지 이동, 뒤로 한 페이지 이동


404 페이지

wildcard(*)를 통해 유저가 잘못된 경로로 접근했을 때 보여줄 페이지를 만들 수 있다.

<Route path="*" element={<div>404. 존재하지 않는 페이지</div>} />

👉 결과

Nested Routes

장점

  • route 작성이 약간 간단해질 수 있다.
  • nested route 접속 시엔 element를 여러 개 보여줄 수 있다. (/about/member 접속 시 About, Memeber 둘 다 보여짐)
  • 뒤로가기 버튼을 이용할 수 있다.
  • 페이지 이동이 쉽다. (UI 스위치 조작이 쉽다.)

/about이 회사정보 보여주는 페이지라고 할 때, 다음과 같은 페이지도 만들고 싶다면?
/about/member
/about/location

다음처럼 만들 수도 있지만,

<Route path="/about" element={} />
<Route path="/about/member" element={} />
<Route path="/about/location" element={} />

Nested Route를 이용할 수도 있다.

// App.js

function App() {
  ...
<Route path="/about" element={<About />}>
  <Route path="member" element={<Member />} />
  <Route path="location" element={} />
</Route>
  ...
}

function About() {
  return (
    <>
    <div>회사정보</div>
    <Outlet></Outlet>
    </>
  )
}
function Member() {
  return (
    <div>Member 페이지</div>
  )
}

🤔 Outlet
Nested Routes의 element를 보여주는 곳은 <Outlet>이다.
따라서 Nested Route(About) 안에 있는 HTML(Member)을 보여주려면 Outlet을 써야 한다.


👉 결과

🤔 Nested Route의 용도
여러 유사한 페이지가 필요할 때 사용할 수 있다.


+) 이벤트 페이지 만들기

Nested Route를 활용해서 이벤트 페이지를 만들 수 있다.

// App.js

function App() {
  ...
<Route path="/event" element={<Event />}>
  <Route path="one" element={<div>첫 주문 시 양배추즙 서비스</div>} />
  <Route path="two" element={<div>생일기념 쿠폰받기</div>} />
</Route>
  ...
}

function Event() {
  return (
    <>
    <h3>오늘의 이벤트</h3>
    <Outlet></Outlet>
    </>
  )
}

URL parameter

위에서는 상세페이지를 만들긴 했지만, 아직 상품명, 정보, 가격 등의 정보를 채워넣지 않은 상태이다.
위의 정보들은 products에 저장되어 있다. 즉, App 컴포넌트 → Detail 컴포넌트로 전송해서 사용할 수 있다.

상품이 3개이므로 상세페이지도 3개 필요 → 여러 개의 페이지를 만들고 싶을 때 URL 파라미터 이용하기!
/detail/0 → 0번째 상품
/detail/1 → 1번째 상품
/detail/2 → 2번째 상품

// App.js

<Route path="/detai/:id" element={<Detail products={products} />} />
// Detail.js

export default function Detail(props) {
  let {id} = useParams();
  
  return (
    <div className="container">
      <div className="row">
        <div className="col-md-6">
          <img src={`/${parseInt(id)+1}.jpg`} width="100%" />
        </div>
        <div className="col-md-6">
          <h4 className="pt-5">{props.products[id].title}</h4>
          <p>{props.products[id].content}</p>
          <p>{props.products[id].price}</p>
          <button className="btn btn-danger">주문하기</button> 
        </div>
      </div>
    </div>
  )
}

🤔 useParams()
유저가 URL 파라미터에 입력한 것을 가져올 수 있다.


+) 정렬버튼이 있다면?

만약 자료의 순서가 변경되는 정렬버튼이 있다면 상세페이지에도 문제가 생길 것이다. 이는 어떻게 해결할 수 있을까? 👉 detail/0 접속 시 0번째 상품을 보여주는 것이 아닌, 상품 id0인 것을 보여주면 된다.

products라는 상품데이터에는 id라는 영구번호가 있다. 따라서 URL에 입력된(/:id) 값과 영구번호(id)가 같은 상품을 찾아서 데이터 바인딩하면 된다.

import { useParams } from 'react-router-dom'

export default function Detail(props) {
  let {id} = useParams();
  let find = props.products.find((product) => parseInt(product.id) === parseInt(id));
  console.log(find);

  return (
    <div className="container">
      <div className="row">
        <div className="col-md-6">
          <img src={`/${parseInt(id)+1}.jpg`} width="100%" />
        </div>
        <div className="col-md-6">
          <h4 className="pt-5">{find.title}</h4>
          <p>{find.content}</p>
          <p>{find.price}</p>
          <button className="btn btn-danger">주문하기</button> 
        </div>
      </div>
    </div>
  )
}

🤔 find()
array 자료 안에서 원하는 항목만 찾아올 수 있다. 다음처럼 쓰면 조건식에 맞는 자료를 찾아서 남겨준다.

array자료.find(()=>{ return 조건식 })
  1. find()는 array 뒤에 붙일 수 있으며 return 조건식을 적으면 된다. 그럼 조건식에 맞는 자료를 남겨준다.
  2. find() 콜백함수에 파라미터를 넣으면 array 자료에 있던 자료를 뜻합니다. (위 코드에서는 product)
  3. product.id === id 라는 조건식을 썼다. array자료.id === url에입력한번호일 경우 결과를 변수에 담아주므로 {상품 1개}가 남을 것이다.

    ❗ 참고로, arrow function에서 return과 중괄호는 동시에 생략이 가능하다.
profile
🙋‍♀️

0개의 댓글