수학과의 좌충우돌 프로그래밍

[React] react의 네비게이션, react-router-dom 본문

웹프로그래밍/React

[React] react의 네비게이션, react-router-dom

ssung.k 2020. 4. 1. 23:24

react-router-dom 설치

react 의react-router-dom에 대해서 알아보도록 하겠습니다.

react-router-dom 은 네이게이션을 만들어주는 패키지로서 별도의 설치가 필요합니다.

다음과 같이 npm을 설치를 진행합시다.

npm install react-router-dom

 

기본적인 설명은 저번 시간까지 진행하였던 영화목록 프로젝트에 이어서 진행을 하도록 하겠습니다.

https://ssungkang.tistory.com/entry/React-Axios-%EC%99%80-CSS

 

react-router-dom을 적용하기 위해 기존의 코드를 약간 수정하고 넘어가겠습니다.

우선 디렉토리 구조를 변경해주었습니다.

src 안에 components 와 routes 를 만들고 routes 안에는 두 개의 route를 만들어주었습니다.

routes는 url마다 대응되는 페이지 개념이고 components 는 route 각 페이지에서 사용할 components 를 넣도록 하겠습니다.

src
├── App.js
├── Fruit.js
├── components
│   └── Movie.js
├── index.js
└── routes
    ├── About.js
    └── Home.js

 

현재 App.js 는 영화 리스트 api와 통신하여 해당 데이터를 movie component로 만들어주고 있습니다.

import React from 'react';
import axios from 'axios';
import Movie from './components/Movie';

class App extends React.Component {
  state = {
    movies: []
  };

  getMovies = async () => {
    const {
      data: {
        data: { movies }
      }
    } = await axios.get("https://yts.mx/api/v2/list_movies.json?sort_by=rating");
    this.setState({ movies });
  }

  componentDidMount() {
    this.getMovies();
  }

  render() {
    const { movies } = this.state;
    return (
      <section className="container">
        <div className="movies">
          {
            movies.map(movie => {
              return <Movie
                key={movie.id}
                year={movie.year}
                title={movie.title}
                summary={movie.summary}
                poster={movie.medium_cover_image}
              />
            })
          }
        </div>
      </section>
    )
  }
}

export default App;

 

이를 복사해서 movies.js 로 옮기고 내용을 약간 수정합시다.

디렉토리 경로가 바뀌었으므로 import 경로를 바꾸고 class name 과 export name을 바꿔주어야 합니다.

import React from 'react';
import axios from 'axios';
import Movie from '../components/Movie';

class Home extends React.Component {
  state = {
    movies: []
  };

  getMovies = async () => {
    const {
      data: {
        data: { movies }
      }
    } = await axios.get("https://yts.mx/api/v2/list_movies.json?sort_by=rating");
    this.setState({ movies });
  }

  componentDidMount() {
    this.getMovies();
  }

  render() {
    const { movies } = this.state;
    return (
      <section className="container">
        <div className="movies">
          {
            movies.map(movie => {
              return <Movie
                id={movie.id}
                year={movie.year}
                title={movie.title}
                summary={movie.summary}
                poster={movie.medium_cover_image}
              />
            })
          }
        </div>
      </section>
    )
  }
}

export default Home;

 

Router와 Route

이제 App.js 에 router를 추가해보도록 하겠습니다.

Route는 url을 기준으로 어떤 component를 불러올지 결정하게 됩니다.

또한 Route 들은 Router로 항상 묶여있어야 하는데 여기서는 HashRouter 를 사용하였지만 다른 Router를 사용해도 무방합니다.

// App.js

import React from "react";
import { HashRouter, Route } from "react-router-dom";
import Home from "./routes/Home";
import About from "./routes/About";

const App = () => {
  return (
    <HashRouter>
      <Route path="/" component={Home} />
      <Route path="/about" component={About} />
    </HashRouter>
  );
}

export default App;

 

about 페이지, /about 으로 가게 되면 Home, About두 개의 component가 둘 다 render 된 것을 확인할 수 있습니다.

왜냐하면 Route는 해당 경로에 해당하는 모든 component를 다 불러오게 됩니다.

이를 해결하기 위해서는 다음과 같이 exact 를 설정해줍니다.

이럴 경우 해당 path로 접근했을 때만 해당 component를 불러오고 뒤에 추가적인 url에 대해서는 불러오지않습니다.

import React from "react";
import { HashRouter, BrowserRouter, Route } from "react-router-dom";
import Home from "./routes/Home";
import About from "./routes/About";

const App = () => {
  return (
    <HashRouter>
      <Route exact path="/" component={Home} />
      <Route path="/about" component={About} />
    </HashRouter>
  );
}

export default App;

 

네비게이션, Link

이제 각 페이지로 이동할 수 있는 네비게이션을 만들어줍시다.

네비게이션은 어느 페이지에서나 보여야 하므로 다음과 같이 component로 추가해주었습니다.

// App.js

import React from "react";
import { HashRouter, Route } from "react-router-dom";
import Home from "./routes/Home";
import About from "./routes/About";
import Navigation from ".components/Navigation";

const App = () => {
  return (
    <HashRouter>
      <Navigation />
      <Route exact path="/" component={Home} />
      <Route path="/about" component={About} />
    </HashRouter>
  );
}

export default App;

 

아직 Navigation component는 만들지 않았으니 만들어주겠습니다.

react가 아닌 그냥 html a 태그를 사용해도 페이지를 바꿀 수 있습니다.

// components/Navigation.js

import React from "react";

const Navigation = () => {
    return <div>
        <a href="/">Home</a>
        <a href="/about">About</a>

    </div>
}

export default Navigation;

 

하지만 이 경우 네비게이션의 해당 링크를 클릭하면 해당 링크로 이동하면서 새로고침이 되고 맙니다.

이는 react와 같은 SPA에서는 원하는 방법이 아닙니다.

따라서 react-router-domLink를 사용해보도록 하죠.

이 경우 새로고침, redirect 없이 바로 페이지를 띄우게 됩니다.

// components/Navigation.js

import React from "react";
import { Link } from "react-router-dom";

const Navigation = () => {
    return <div>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
    </div>
}

export default Navigation;

 

그리고 Link 는 항상 Router안에서 존재해야 합니다.

현재는 Navigation.jsHashRouter 안에 있으므로 문제 없이 동작하는 걸 확인할 수 있습니다.

 

Route Props

이번에는 각 영화 데이터를 클릭할 시 상세 페이지로 이동하도록 하겠습니다.

Movie.jsLink를 사용하여 값을 넘겨줍니다.

to 인자에 object로서 경로는 pathname을 통해서 값은 state를 통해서 넘겨줄 수 있습니다.

// components/Movie.js

import React from "react";
import PropTypes from "prop-types";
import { Link } from "react-router-dom";

const Movie = ({ id, year, title, summary, poster }) => {
  return (
    <Link to={{
      pathname: `/movie/${id}`,
      state: {
        year:year,
        title:title,
        summary:summary,
        poster:poster
      }
    }}>
      <div className="movie">
        <img src={poster} alt={title} title={title} />
        <div className="movie__data">
          <h3 className="moivie__title">{title}</h3>
          <h5 className="moivie__year">{year}</h5>
          <p className="moivie__summary">{summary}</p>
        </div>
      </div >
    </Link>
  );
}

Movie.propTypes = {
  year: PropTypes.number.isRequired,
  title: PropTypes.string.isRequired,
  summary: PropTypes.string.isRequired,
  poster: PropTypes.string.isRequired
};

export default Movie;

 

다음으로는 App.jsmovie/:id로 가는 route를 추가해줍시다.

// App.js

import React from "react";
import { HashRouter, Route } from "react-router-dom";
import Home from "./routes/Home";
import About from "./routes/About";
import Detail from "./routes/Detail";
import Navigation from "./components/Navigation";

const App = () => {
  return (
    <HashRouter>
      <Navigation />
      <Route exact path="/" component={Home} />
      <Route path="/about" component={About} />
      <Route path="/movie/:id" component={Detail} />
    </HashRouter>
  );
}

export default App;

 

이제 Detail.js를 다음과 같이 만들어줍시다.

Router에서 각 Route 에 기본적인 props를 넘겨줍니다.

console.log 를 통해서 어떤 props들이 넘어가는지 확인해봅시다.

import React from "react";

const Detail = (props) => {
    console.log(props);
    return <span>hello</span>;
}

export default Detail;

 

console에 찍힌 결과입니다.

우선 props.location.stateto 인자를 통해서 넘긴 값이 담긴 것을 확인할 수 있습니다.

그 외에도 history, location, match, staticContext 등 다양한 값들이 보입니다.

이들에 대해서는 별도의 포스팅에서 좀 더 자세히 다뤄보도록 하겠습니다.

{history: {…}, location: {…}, match: {…}, staticContext: undefined}
  history: {length: 20, action: "PUSH", location: {…}, createHref: ƒ, push: ƒ, …}
  location:
    pathname: "/movie-detail"
      state:
      year: 2013
      title: "Doctor Who The Day of the Doctor"
      summary: "In 2013, something terrible is awakening in London's National Gallery; in 1562, a murderous plot is afoot in Elizabethan England; and somewhere in space an ancient battle reaches its devastating conclusion."
      poster: "https://yts.mx/assets/images/movies/doctor_who_the_day_of_the_doctor_2013/medium-cover.jpg"
			__proto__: Object
      search: ""
      hash: ""
  	__proto__: Object
  match: {path: "/movie-detail", url: "/movie-detail", isExact: true, params: {…}}
  staticContext: undefined
  __proto__: Object

 

Route Props 문제점

그런데 문제점이 생깁니다.

Home 에서 movie를 클릭해서 detail 페이지로 이동할 시에는 데이터를 담아서 가지만 해당 url로 직접 접근할 경우에는 props.location.state 의 값이 undefined 로 표시됩니다.

따라서 이럴 경우에는 따로 처리를 해줘야하는데 Home 으로 이동시켜보도록 하겠습니다.

react-route-domhistory 를 이용하면 원하는 url로 이동할 수 있습니다.

// routes/Detail.js

import React, { useEffect } from "react";

const Detail = (props) => {
    useEffect(()=>{
        if (props.location.state === undefined){
            props.history.push("/");
        }
    })
    return <span>{props.location.state.title}</span>;
}

export default Detail;

 

다음 문제점은 새로고침 할 때 일어납니다.

새로고침할 시에 state들이 유지가 안되기 때문에 props.location.state 값이 없어 에러가 발생합니다.

useEffect 에서 home으로 redirect 시키기 전에 먼저 render가 일어나 화면에 값을 뿌려주기 때문이죠.

다음과 같이 분기처리함으로서 해결할 수 있습니다.

// routes/Detail.js

import React, { useEffect } from "react";

const Detail = (props) => {
    useEffect(()=>{
        if (props.location.state === undefined){
            props.history.push("/");
        }
    })
     if (props.location.state){
        return <span>{props.location.state.title}</span>;
    } else {
        return null;
    }
}

export default Detail;

 

'웹프로그래밍 > React' 카테고리의 다른 글

[react] react-create-app의 Proxy  (0) 2020.02.27
[React] axios 의 withCredentials  (0) 2020.02.17
[React] Axios 와 CSS  (0) 2020.01.27
[React] Component Life Cycle method  (0) 2020.01.26
[React] Class Component 와 State  (1) 2020.01.26
Comments