TIL 57 | 동적라우팅 Dynamic Routing (상세페이지 이동)

hyounglee·2020년 9월 19일
0

React

목록 보기
16/33
post-thumbnail

상품 리스트 페이지에서 해당 상품을 클릭 시 상세 페이지로 넘어가는 기능은 단순히 path를 바꿔준다고 되는 것이 아니다. 리액트의 3rd party 라이브러리를 사용하여 동적라우팅을 구현해보자.

참고자료 : React | Query parameters, URL parameters

React Router

// Routes.js
import React from 'react';
import {
  BrowserRouter as Router,
  Route,
  Switch,
} from 'react-router-dom';

import Home from './Pages/Home';
import List from './Pages/List';
import Detail from './Pages/Detail';

class Routes extends React.Component {
  render() {
    return (
      <Router>
        <Switch>
          <Route exact path="/" component={Home} />
          <Route exact path="/list" component={List} />
          <Route exact path="/detail" component={Detail} />
        </Switch>
      </Router>
    )
  }
}

export default Routes;

수백개의 상세페이지는 각각 따로 url을 부여하고 만드는 것이 아니라 해당 상품의 id 값으로 그때 그때 컴포넌트를 렌더해서 불러온다고 생각하면 된다. 위 코드에서는 url path로 /detail 설정 시 해당 컴포넌트로 이동하시만 어떤 상품을 보여줄지는 지정이 되지 않는다. 유동 데이터를 사용하면 이런 문제를 해결할 수 있다.

동적 라우팅

동적 라우팅은 라우트의 경로에 특정 값을 넣어서 해당 페이지로 이동할 수 있게 하는 것이다. React Router에서는 두가지 방법으로 이 기능을 구현할 수 있다.

라우트로 설정한 컴포넌트의 3가지 props

컴포넌트를 라우트로 설정하게 되면 3가지의 props를 전달받는다.

  1. history
    이 객체를 통해서 push, replace로 다른 경로로 이동하거나 앞 뒤 페이지로 전환할 수 있다. 컴포넌트 이동시에 this.props.history.push('/page')를 사용했던 것을 기억하면 된다.

  2. location
    이 객체는 현재 경로에 대한 정보를 지니고 있고, URL 쿼리 (/company_list?category=12)에 대한 정보를 가지고 있다.

  3. match
    이 객체에는 어떤 라우트에 매칭이 되었는지에 대한 정보가 있고 params(/company_detail/:id)에 대한 정보를 갖고 있다.

상품리스트 ➔ 상세페이지

예제를 보면서 이해해보자!

Routes.js

import React, { Component } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Monsters from "./Assignments/Monsters/Monsters";
import Pagination from "./Lectures/Pagination/Photos";
import UrlParameters from "./Lectures/UrlParameters/Monsters";
import MonsterDetail from "./Lectures/UrlParameters/MonsterDetail";
import StateProps from "./Lectures/StateProps/StateProps";
import OnChange from "./Lectures/OnChange/OnChange";
import MenuTab from "./Lectures/MenuTab/MenuTab";

export default class Routes extends Component {
  render() {
    return (
      <Router>
        <Switch>
          <Route exact path="/" component={Monsters} />
          <Route exact path="/monsters" component={UrlParameters} />
          <Route exact path="/monsters/detail" component={MonsterDetail} />
          <Route exact path="/monsters/detail/:id" component={MonsterDetail} />
        </Switch>
      </Router>
    );
  }
}

index 화면의 List 안에 있는 카드를 클릭 시 해당 monster의 상세 페이지로 넘어가는 형태를 구현해보자. /monsters/detailMonstersDetail 컴포넌트를 연결했고, 지정된 몬스터의 상세페이지를 보여주기 위해서

<Route exact path="/monsters/detail/:id" component={MonsterDetail} />

이렇게 id라는 params를 추가한 라우트를 추가해주었다.

Card.js

import React, { Component } from "react";
import { withRouter } from "react-router-dom";
import "./Card.scss";

class Card extends Component {
  render() {
    console.log("card: ", this.props);
    const { id, name, email } = this.props;
    return (
      <div
        className="card-container"
        onClick={() => this.props.history.push(`/monsters/detail/${id}`)}
      >
        <img src={`https://robohash.org/${id}?set=set2&size=180x180`} alt="" />
        <h2>{name}</h2>
        <p>{email}</p>
      </div>
    );
  }
}

export default withRouter(Card);

리스트 페이지 안에서 Card 컴포넌트가 map 함수를 통해 렌더된다. Card 컴포넌트는 Route.js 파일에서 import 되어 사용되지 않았기 때문에 withRouter를 import 해주고, export 에서 감싸주었다.

onClick={() => this.props.history.push(`/monsters/detail/${id}`)}

card-container에 onClick 이벤트를 걸어서 해당 card의 props의 id 값이 담긴 url 주소로 push 해준다. 위에서 배운 라우트의 props인 history.push를 사용한 것을 확인할 수 있다. 이렇게 상세페이지로 이동하게 한다.

MonsterDetail.js

import React, { Component } from "react";
import Card from "./Components/Card/Card";
import "./MonsterDetail.scss";

class MonsterDetail extends Component {
  state = {
    data: {},
  };

  componentDidMount() {
    fetch(
      `https://jsonplaceholder.typicode.com/users/${this.props.match.params.id}`
    )
      .then((res) => res.json())
      .then((res) => this.setState({ data: res }));
  }

  componentDidUpdate(prevProps) {
    if (prevProps.match.params.id !== this.props.match.params.id) {
      fetch(
        `https://jsonplaceholder.typicode.com/users/${this.props.match.params.id}`
      )
        .then((res) => res.json())
        .then((res) => this.setState({ data: res }));
    }
  }

  render() {
    const { data } = this.state;
    return (
      <div className="url-parameters">
        <div className="btn-wrapper">
          <button>Back to Monsters List</button>
        </div>
        <Card id={data.id} name={data.name} email={data.email} />
        <div className="btn-wrapper">
          <button
            onClick={() =>
              this.props.history.push(
                `/monsters/detail/${this.props.match.params.id - 1}`
              )
            }
          >
            Previous
          </button>
          <button
            onClick={() =>
              this.props.history.push(
                `/monsters/detail/${+this.props.match.params.id + 1}`
              )
            }
          >
            Next
          </button>
        </div>
      </div>
    );
  }
}

export default MonsterDetail;

Detail 페이지에서는 부모 컴포넌트에서 넘어온 id 값이 담긴 상세 페이지 컴포넌트를 렌더한다.

componentDidMount() {
    fetch(
      `https://jsonplaceholder.typicode.com/users/${this.props.match.params.id}`
    )
      .then((res) => res.json())
      .then((res) => this.setState({ data: res }));
  }

컴포넌트가 마운트 되었을 때 해당 id의 json data를 state에 업데이트 해준다.

componentDidUpdate(prevProps) {
    if (prevProps.match.params.id !== this.props.match.params.id) {
      fetch(
        `https://jsonplaceholder.typicode.com/users/${this.props.match.params.id}`
      )
        .then((res) => res.json())
        .then((res) => this.setState({ data: res }));
    }
  }

상세페이지에서 앞 뒤의 id 값이 담긴 다른 아이템의 페이지로 이동하기 위해서 Update 함수를 추가해주었다. 현재의 match.params.id 와 이전의 match.params.id 값이 달라지면, 새로운 데이터를 fetch 하여 랜더한다.

<button
  onClick={() =>
    this.props.history.push(
      `/monsters/detail/${this.props.match.params.id - 1}`
    )
  }
>
  Previous
</button>
<button
  onClick={() =>
    this.props.history.push(
      `/monsters/detail/${+this.props.match.params.id + 1}`
    )
  }
>
  Next
</button>

상세페이지에서 previous, next 아이템으로 이동할 수 있는 버튼이다. 현재의 match.params.id+1, -1 값으로 이동하는 방식이다.

Next 버튼의 path에서 +this.props.match.params.id+를 붙인 이유는, 마이너스 계산은 자동적으로 int로 변경되는 반면 플러스는 string에 '1' 이 뒤에 붙는 형식으로 주소가 변경되기 때문이다.

profile
(~˘▾˘)~♫❝ 쉽게만 살아가면 재미없어 빙고 .ᐟ ❞•*¨*•.¸¸♪

0개의 댓글