⚛️ React

리액트로 쇼핑몰 사이트 만들기 #3 상세페이지(router)

복숭아아이스티에샷추가 2024. 5. 10. 14:00

    제품을 누르면 제품의 상세페이지를,

    상단바의 cart 를 누르면 장바구니 페이지를 보여주는 기능을 구현할 것이다.

     

    프레임워크 없이 html 만을 이용해서 구현하는 방법은 새로운 html 파일을 만들어서 상세페이지들을 작성한 후 연결했어야했다.

    하지만 리액트를 사용하면 html 파일은 단 하나만 필요하다.

     


     

    리액트로 상세페이지 구현하는 방법

     

    1. 필요한 페이지들을 컴포넌트로 각각 만든다.

    2. 누군가 해당 페이지에 접속하면 그 컴포넌트를 보여준다.

     

     

    하지만 이러면 html 파일 속 코드 길이가 너~무 길어진다

     

     

     

    (진짜) 리액트로 상세페이지 구현하는 방법

    :  react-router-dom 라이브러리  를 이용한다. 일명  라우팅 


     

     

    1. 먼저 router 라이브러리를 설치하기 위해 터미널에 아래와 같이 입력한다.

    npm install react-router-dom@6

     

     

    2. index.js 파일에 들어가서 <BrowserRouter> 태그를 추가해준다.

    (왼) 변경 전 / (오) 변경 후 

     

     

    3. App.js 파일에서 Route, Routes, Link 를 import 한다.

    import { Routes, Route, Link } from 'react-router-dom';

     

     

     Route 컴포넌트 

    : 간단하게 페이지 라고 생각하면 된다. 만약 페이지 3개를 만들 것이다? 그러면 아래처럼 구현

    function App() {
        return (
            <div className="App">
                <Routes>
                    <Route />
                    <Route />
                    <Route />
                </Routes>
                ...
            </div>
        )
    }
    <Router path="/이동url" element={해당 페이지에서 보여줄 코드} />

     

    현재 브라우저의 location 상태에 따라 다른 element를 렌더링한다.

    Route.element: 조건이 맞을 때 렌더링할 element, <Element />의 형식으로 전달된다.

    Route.path: 현재 path값이 url과 일치하는지 확인해 해당 url에 매칭된 element를 렌더링해준다.

     

     Routes 컴포넌트 

    : 모든 Route의 상위 경로에 존재해야 하며, location 변경 시 하위에 있는 모든 Route를 조회해 현재 location과 맞는 Route를 찾아준다.

     

     

     Link 컴포넌트 

    : 라우터 내에서 직접적으로 페이지 이동 버튼

    <Link to="/이동url">아무거나이름</Link>

     

     

     


     

     

    그래서 한번 상세페이지를 만들어보자!

    // App.js
    // function App 안에 아래의 코드 작성
    
    <Routes>
            {/* 메인페이지 */}
            <Route path="/" element={
              <>
              <div className="main-bg" style={{ backgroundImage : 'url('+ mainImg +')' }}></div>
              <div className="container">
                <div className="row">
                  { shoes.map((a, i)=> {
                      return <Card shoes={shoes[i]} i={i}></Card>
                    })}
                </div>
              </div>
              </>
            } />
            {/* 상세페이지 */}
            <Route path="/detail" element={<div>상세페이지 보여주는 척</div>} />
    </Routes>

     

     

    그리고 홈 화면과 상세페이지를 보여줄 링크를 만든다. 따로 만들어도 상관없지만 상단바에 위치하면 더 어울릴 것 같다.

    <Navbar bg="dark" data-bs-theme="dark">
        <Container>
            <Navbar.Brand href="#home">Shopping</Navbar.Brand>
                <Nav className="me-auto">
                    {/* 페이지 이동버튼 */}
                    <Link to="/">홈</Link>
                    <Link to="/detail">상세페이지</Link>
                </Nav>
        </Container>
    </Navbar>

     

     

    이러면 완성!

    왼쪽은 홈화면(기본), 오른쪽은 상세페이지를 눌렀을 때 나오는 화면

     

     


     

    상세페이지 컴포넌트로 만들기

     

    화면에 나온 신발들의 상세페이지를 만드려고 한다.

     

    Detail 이라는 함수의 컴포넌트를 만들 것이다.

    이 함수는 길기 때문에 App.js 파일에 작성하지 않고 따로 Detail.js 파일을 만들 것이다.

    function Detail(){
      return (
        <div className="container">
          <div className="row">
            <div className="col-md-6">
              <img src="https://codingapple1.github.io/shop/shoes1.jpg" width="100%" />
            </div>
            <div className="col-md-6 mt-4">
              <h4 className="pt-5">상품명</h4>
              <p>상품설명</p>
              <p>12,000원</p>
              <button className="btn btn-danger">주문하기</button>
            </div>
          </div>
        </div>
      )
    }
    
    export default Detail;

     

    element 안에 <Detail /> 태그를 입력하면 된다.

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

     

     

    이렇게 하다보면 파일들이 점점 많아질 것이다. 

    따라서 비슷한 것끼리 하나의 폴더에 모아주면 된다. 

    예를 들면, 페이징 기능하는 컴포넌트를 모으기 위해

    src 폴더에 routes 라는 폴더를 만들고, 이 폴더 안에 전부 넣어주면 관리하기 편할 것이다.

    경로도 모두 수정해야한다

     


     

    [ useNavigate ]

     

    페이지 이동을 도와주는 리액트 훅

    import { Routes, Route, Link, useNavigate } from 'react-router-dom';
    
    {/* 변수 선언 */}
    let navigate = useNavigate()
    
    
    
    return (
        <div className="App"> 
          <Navbar>
              <Container>
                <Nav className="me-auto">
                  {/* navigate 적용 */}
                  <Nav.Link onClick={()=>{ navigate('/이동할url') }}>화면에 보여지는</Nav.Link>
                </Nav>
              </Container>
          </Navbar>
    )

     

    "화면에 보여지는" 이라는 버튼을 누르면 해당 함수가 실행되어 작성한 url 로 이동한다.

     

     

    만약 

    navigate(-1)

     

    로 작성하면 뒤로 간다.

    (-2, 1, 2 도 다 가능) 

     


     

    [ 404 페이지 ]

    <Route path="*" element={<div>없는 페이지</div>} />

     


     

    [ nested routes & outlet ]

     

    유사한 페이지 여러개를 구현하고 싶을 때 사용한다.

     

    예를 들어 /about 페이지를 만들고,

    이 안에 /about/member 페이지, /about/location 페이지 를 만들고 싶다면

    import { Routes, Route, Outlet } from 'react-router-dom';
    
    ...
    
    <Routes>
        <Route path="/about" element={<About/>}>
            <Route path="member" element={<div>멤버 페이지</div>} />
            <Route path="location" element={<div>위치정보 페이지</div>} />
        </Route>
    </Routes>
    
    ...
    
    function About(){
      return (
        <div>
          <h4>회사정보 페이지</h4>
          <Outlet></Outlet>
        </div>
      )
    }

     

    Outlet 태그를 사용하지 않는다면 member, location 의 html 코드가 보이지 않는다.

     

     

    이렇게 router 를 사용해도 동적인 UI 를 만들 수 있다.

    뒤로 가기 버튼도 수월하게 사용 가능하다.

     


     

    [ url 파라미터 ]

     

     상세페이지가 여러개 생긴다면  router 를 여러개 만들면 될 것같다.

    하지만 페이지가 100개라면!?

     

    이럴때 사용하는 것이  url 파라미터  이다.

     

    <Route path="/detail/:id" element={<Detail shoes={shoes} />} />

     

    위 코드는 "/detail/어쩌구 에 접속하면 <Detail shoes={shoes} /> 로 이동해주세요" 라는 의미

     

     

    그리고 Detail.js 파일에 가서

    import { useParams } from 'react-router-dom';
    
    function Detail(props){
        let {id} = useParams();
        
        return (
        ...
        <div>
            <h4 className="pt-5">{props.shoes[id].title}</h4>
            <p>{props.shoes[id].content}</p>
            <p>{props.shoes[id].price}원</p>
            <button className="btn btn-danger">주문하기</button>
        </div>
        ...
        )
    }

     

    이러면 detail/1, detail/2. detail/3 url 이동할 때마다 화면에 나타나는 정보가 달라진다.