프로그래밍/JavaScript

[정리] 러닝 리액트 11장 - React Router

seungdols 2018. 7. 14. 10:29

Chapter 11. React Router

라우팅은 클라이언트의 요청을 처리할 종말점을 찾는 과정이라고 책에서 설명하고 있는데, 쉽게 말하자면, 웹 페이지의 메뉴 버튼을 생각하면 어떠한 일을 하는 것인지 이해하기가 쉽다.

결국 사용자는 원하는 페이지로의 이동을 원하게 되는데, 해당 페이지로 이동을 가능케하는 것이 Router의 역할이다.

11.1 라우터 사용하기

export const MainMenu = () =>
<nav className="main-menu">
<NavLink to="/"><HomeIcon/></NavLink>
<NavLink to="/about" activeStyle={selectedStyle}>[회사 소개]</NavLink>
<NavLink to="/events" activeStyle={selectedStyle}>[이벤트]</NavLink>
<NavLink to="/products" activeStyle={selectedStyle}>[제품]</NavLink>
<NavLink to="/contact" activeStyle={selectedStyle}>[고객 지원]</NavLink>
</nav>

export const AboutMenu = ({ match }) =>
<div className="about-menu">
<li>
<NavLink to="/about"
style={match.isExact && selectedStyle}>
[회사]
</NavLink>
</li>
<li>
<NavLink to="/about/history"
activeStyle={selectedStyle}>
[연혁]
</NavLink>
</li>
<li>
<NavLink to="/about/services"
activeStyle={selectedStyle}>
[서비스]
</NavLink>
</li>
<li>
<NavLink to="/about/location"
activeStyle={selectedStyle}>
[위치]
</NavLink>
</li>
</div>

라우터를 사용하면, 이 웹사이트의 각 섹션에 대한 경로를 설정할 수 있다. 각 경로는 브라우저의 주소창에 넣을 수 있는 Endpoint를 뜻한다.

Hash Router

react-router-dom은 단일 페이지 앱의 내비게이션 이력을 관리할 수 있는 다양한 방법을 제공한다. HashRouter는 클라이언트를 위해 설계된 라우터다. Hash(#)는 앵커링크를 정의할 때 쓰였다. 주소창의 현재 페이지 경로 뒤에 # 식별자를 입력하면 브라우저는 서버 요청을 보내지 않고, 현재 페이지에서 식별자에 해당하는 id 애트리뷰트가 있는 엘리먼트를 찾아서 그 엘리먼트의 위치를 화면에 보여준다.

HashRouter를 사용하면 모든 경로 앞에 #을 붙여야한다. HashRouter는 백엔드가 필요 없는 작은 클라이언트 사이트를 만들 때 유용한 도구다. BrowserRouter는 대부분의 상용 어플리케이션에 적합한 라우터다.

import {
HashRouter,
Route,
Switch,
Redirect
} from 'react-router-dom'

import {
Home,
About,
Events,
Products,
Contact,
Whoops404
} from './pages'

window.React = React

render(
<HashRouter>
<div className="main">
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Redirect from="/history" to="/about/history" />
<Redirect from="/services" to="/about/services" />
<Redirect from="/location" to="/about/location" />
<Route path="/events" component={Events} />
<Route path="/products" component={Products} />
<Route path="/contact" component={Contact} />
<Route component={Whoops404} />
</Switch>
</div>
</HashRouter>,
document.getElementById('react-container')
)

위 어플리케이션은 시작시 App컴포넌트 대신에 HashRouter 컴포넌트를 렌더링한다. 각 경로는 HashRouter 내부에 Route 컴포넌트로 정의된다.

이렇게 정의한 Route와 윈도우의 주소에 따라 라우터가 렌더링할 컴포넌트가 결정된다. 각 Route 컴포넌트에는 Path와 component 프로퍼티가 있다.

주소가 path와 일치하면 component가 표시된다. 위치가 /면 라우터는 Home 컴포넌트를 렌더링한다. 위치가 /products면 라우터는 Products 컴포넌트를 렌더링한다.

react-router-dom은 브라우저 링크를 만들어주는 Link라는 컴포넌트를 제공한다.

import { Link, Route } from 'react-router-dom'
import { MainMenu, AboutMenu } from './menus'
import './stylesheets/pages.scss'

const PageTemplate = ({children}) =>
<div className="page">
<MainMenu />
{children}
</div>

export const Home = () =>
<div className="home">
<h1>[회사 웹사이트]</h1>
<nav>
<Link to="about">[회사 소개]</Link>
<Link to="events">[이벤트]</Link>
<Link to="products">[제품]</Link>
<Link to="contact">[고객 지원]</Link>
</nav>
</div>

링크를 클릭함으로써 홈페이지 내부 페이지에 접근 할 수 있다.

11.1.1 라우터 프로퍼티

리액트 라우터는 렌더링하는 컴포넌트에 프로퍼티를 넘긴다. 현재 위치를 프로퍼티에서 얻을 수 있기도 하다.

export const Whoops404 = ({ location }) =>
<div className="whoops-404">
<h1>'{location.pathname}' 경로의 자원을 찾을 없습니다.</h1>
</div>

위 컴포넌트는 정의되지 않은 경로에 해당하는 주소를 주소창에 입력한 경우 렌더링 된다. 일반적으로 잘못된 페이지 링크를 노출시키기위해 사용한다.

render(
<HashRouter>
<div className="main">
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Redirect from="/history" to="/about/history" />
<Redirect from="/services" to="/about/services" />
<Redirect from="/location" to="/about/location" />
<Route path="/events" component={Events} />
<Route path="/products" component={Products} />
<Route path="/contact" component={Contact} />
<Route component={Whoops404} />
</Switch>
</div>
</HashRouter>,
document.getElementById('react-container')
)

Switch 컴포넌트는 경로가 일치하는 Route에서 첫 번째 경로를 렌더링한다. 따라서 여러 Route 중 오직 하나만 렌더링하도록 보장할 수 있다. 경로와 일치하는 Route가 하나도 없다면, path 프로퍼티가 없는 맨 마지막 Route가 표시된다.

모든 Route 컴포넌트를 라우터로 감싸야 한다. 위의 예제에서는 HashRouter를 사용했고, 윈도우의 현재 위치에 따라 렌더링할 컴포넌트를 선택해준다. Link 컴포넌트를 사용하면, 내비게이션을 지원할 수 있다.

11.2 경로 내포시키기

Route 컴포넌트는 특정 URL과 일치할 때 표시해야 하는 컨텐츠와 함께 쓰인다. 이 기능을 활용해서 웹앱을 명확한 구조로 조직하면 컨텐츠 재사용을 촉진 시킬 수 있다.

11.2.1 페이지 템플릿 사용

const selectedStyle = {
backgroundColor: "white",
color: "slategray"
}

export const MainMenu = () =>
<nav className="main-menu">
<NavLink to="/"><HomeIcon/></NavLink>
<NavLink to="/about" activeStyle={selectedStyle}>[회사 소개]</NavLink>
<NavLink to="/events" activeStyle={selectedStyle}>[이벤트]</NavLink>
<NavLink to="/products" activeStyle={selectedStyle}>[제품]</NavLink>
<NavLink to="/contact" activeStyle={selectedStyle}>[고객 지원]</NavLink>
</nav>

페이지 템플릿 컴포넌트를 만들기 위해서 메인 메뉴 컴포넌트가 필요로 하다.

const PageTemplate = ({children}) =>
<div className="page">
<MainMenu />
{children}
</div>

페이지 템플릿에 있는 children 부분이 각 섹션이 렌더링 될 부분이며, pageTemplate을 사용해서 각 섹션을 합성할 수 있다.

export const Events = () =>
<PageTemplate>
<section className="events">
<h1>[이벤트 달력]</h1>
</section>
</PageTemplate>

export const Products = () =>
<PageTemplate>
<section className="products">
<h1>[제품 목록]</h1>
</section>
</PageTemplate>

export const Contact = () =>
<PageTemplate>
<section className="contact">
<h1>[고객 지원]</h1>
</section>
</PageTemplate>
리다이렉션 사용하기

<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Redirect from="/history" to="/about/history" />
<Redirect from="/services" to="/about/services" />
<Redirect from="/location" to="/about/location" />
<Route path="/events" component={Events} />
<Route path="/products" component={Products} />
<Route path="/contact" component={Contact} />
<Route component={Whoops404} />

11.3 라우터 파라미터

리액트 라이터의 다른 유용한 특징은 라우터 파라미터를 설정할 수 있는 기능이다. 라우터 파라미터는 URL에서 값을 얻을 수 있는 변수로, 컨텐츠를 걸러내거나 사용자 선호에 따라 여러 표시 방법을 제공해야 하는 데이터 주도 웹 어플리케이션 개발에 유용하다.

11.3.1 색 상세 페이지 추가하기

export const findById = compose(
getFirstArrayItem,
filterArrayById
)

array-helper.js 내에 보면, 저장된 특정 색을 지정 할 수 있는데, id 필드로 객체를 찾는 findById 함수를 만들 수 있다.

또한 url상에서 파라미터 값을 추출할수 있다. 이를 라우터 파라미터를 사용하면 얻을 수 있는데, 라우터 파라미터는 콜론(:)으로 시작한다.

<Route exact path="/:id" component={UniqueIDHeader} />

Route안에 id라는 이름의 파라미터로 url의 값이 저장된다.

UniqueIDHeader 컴포넌트는 id를 match.params 객체에서 얻을 수 있다.

const UniqueIDHeader = ({match}) => <h1>{match.params.id}</h1>

여러 파라미터를 사용하는 것도 가능하다.

경로에서 여러 파라미터를 만들면, 한 파라미터 객체 안에서 모든 파라미터를 사용할 수 있다.

<Route path="/members/:gender/:state/:city" component="Member"/>

이 세 가지 파라미터를 URL로 초기화 할 수 있다.

URL에서 넘어온 이 값들은 모두 Member 컴포넌트에 전달되고, 해당 컴포넌트에서는 match.params 객체를 통해 모두 얻을 수 있다.

사용자가 어떤 색을 선택한 경우 렌더링해야 하는 ColorDetails 컴포넌트를 만들자.

const ColorDetails = ({ titile, color}) =>
<div className="color-details"
style={{backgroundColor: color}}>
<h1>{title}</h1>
<h1>{color}</h1>
</div>

위 컴포넌트는 자세한 색정보를 프로퍼티에서 얻는다.

색관리 앱은 리덕스를 사용하기 때문에 다음과 같이 라우터 파라미터로부터 가져온 사용자가 선택한 색을 상태에서 찾아내 표현 컴포넌트에 전달해주는 컨테이너를 새로 만들어야 한다.

export const Color = connect(
(state, props) => findById(state.colors, props.match.params.id)
)(ColorDetails)

connect HOC를 사용해서 Color 컨테이너를 만든다. connect HOC는 찾은 객체의 데이터를 ColorDetails 컴포넌트의 프로퍼티로 만들어준다.

connect HOC는 Color 컨테이너에 전달된 모든 프로퍼티를 ColorDetails 컴포넌트의 프로퍼티로 변환하게 되어 라우터가 전달한 모든 프로퍼티가 전달된다.

const App = () =>
<Switch>
<Route exact path="/:id" component={Color} />
<Route path="/"
component={() => (
<div className="app">
<Route component={Menu} />
<NewColor />
<Switch>
<Route exact path="/" component={Colors} />
<Route path="/sort/:sort" component={Colors} />
<Route component={Whoops404} />
</Switch>
</div>
)} />
</Switch>

export default App

withRouter를 사용하면 컴포넌트 프로퍼티로 라우터의 history를 얻을 수 있다. 이 history를 사용해 Color컴포넌트 안에서 내비게이션을 제공할 수 있다.

class Color extends Component {

render() {
const { id, title, color, rating, timestamp, onRemove, onRate, history } = this.props
return (
<section className="color" style={this.style}>
<h1 ref="title"
onClick={() => history.push(`/${id}`)}>{title}</h1>
<button onClick={onRemove}>
<FaTrash />
</button>
<div className="color"
onClick={() => history.push(`/${id}`)}
style={{ backgroundColor: color }}>
</div>
<TimeAgo timestamp={timestamp} />
<div>
<StarRating starsSelected={rating} onRate={onRate}/>
</div>
</section>
)
}

}

Color.propTypes = {
title: PropTypes.string.isRequired,
color: PropTypes.string.isRequired,
rating: PropTypes.number,
onRemove: PropTypes.func,
onRate: PropTypes.func
}

Color.defaultProps = {
rating: 0,
onRemove: f=>f,
onRate: f=>f
}

export default withRouter(Color)

withRouter는 HOC다. Color 컴포넌트를 익스포트하면 withRouter를 호출한다. withRouterColor컴포넌트를 래핑해서 라우터 프로퍼티인 match, history, location등을 전달해준다.

11.3.2 색 정렬 상태를 라우터로 옮기기

  export const Colors = connect(
({colors}, {match}) =>
({
colors: sortColors(colors, match.params.sort)
}),
dispatch =>
({
onRemove(id) {
dispatch(removeColor(id))
},
onRate(id, rating) {
dispatch(rateColor(id, rating))
}
})
)(ColorList)

ColorList에 프로퍼티로 색을 넘기기 전에 경로 파라미터에 따라 색을 정렬한다는 점에 유의하라.

const selectedStyle = { color: 'red' }

const Menu = ({ match }) =>
<nav className="menu">
<NavLink to="/" style={match.isExact && selectedStyle}>date</NavLink>
<NavLink to="/sort/title" activeStyle={selectedStyle}>title</NavLink>
<NavLink to="/sort/rating" activeStyle={selectedStyle}>rating</NavLink>
</nav>

Menu.propTypes = {
sort: PropTypes.string
}

export default Menu

사용자는 URL을 통해 정렬 방식을 지정할 수 있다. 특별히 지정한 정렬 파라미터가 없다면 날짜로 색을 정렬한다.

이 메뉴는 현재 지정된 데이터 정렬 방식을 사용자가 알 수 있도록 해당 항목의 색을 다르게 표현한다.

const App = () =>
<Switch>
<Route exact path="/:id" component={Color} />
<Route path="/"
component={() => (
<div className="app">
<Route component={Menu} />
<NewColor />
<Switch>
<Route exact path="/" component={Colors} />
<Route path="/sort/:sort" component={Colors} />
<Route component={Whoops404} />
</Switch>
</div>
)} />
</Switch>

export default App

반응형