본문 바로가기
IT/React

React - 라우팅

by 최고영회 2019. 4. 19.
728x90
반응형
SMALL

과거에는 동적 컨텐츠가 서버에 의해 생성됐다. 
서버는 DB로부터 데이터를 조회한 후 이를 이용해 HTML  뷰를 생성해 브라우저로 전달했다. 
요즘에는 더 많은 애플리케이션 로직이 JavaScript에 의해 관리되는 클라이언트에서 실행된다. 
최초에는 서버가 HTML, JavaScript, CSS를 내려보내지만 그 다음부터는 클라이언트 리액트 앱이 모든 것을 관리한다. 
그 시점부터는 사용자가 페이지를 직접 새로고치지 않는 한 서버는 오로지 JSON 데이터만을 전달한다. 

리액트에서의 라우팅
- Router와 Route라는 두개의 컴포넌트를 개발한다. 
- Router 는 Route 컴포넌트들로 구성된다. 
- 각 Route는 /, /posts/123 등 하나의 URL 경로를 표현하며 해당 URL에 매핑한다. 
- 사용자가 / 경로를 방문하면 그 경로에 해당하는 컴포넌트가 표시된다.
- Router 컴포넌트는 보통의 리액트 컴포넌트로 보이지만(render/컴포넌트 메서드를 가지고 있으며 JSX를 활용)
  컴포넌트를 UIRL과 매핑하는 기능을 실행한다. 
- Route 컴포넌트는 /users/:user와 같이 매개변수를 정의한다. :user 문법은 컴포넌트에 전달되는 값을 의미한다. 
- Link 컴포넌트를 구현해 클라이언트 측 라우터에 탐색 기능을 구현한다. 

import PropTypes from 'prop-types';
import {Component} from 'react';
import invariant from 'invariant';

class Route extends Component {
  static propTypes = {
    path: PropTypes.string,
    component: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
  };
  render(){
    return invariant(false, "<Route> elements are for config only and shoud't be rendered");
  }
}

export default Route;

예제 코드에서 사용한 invariant는 특정 조건을 만족하는 경우 에러를 발생시키는 도구다. 
위 예제에서는 Route 컴포넌트가 아무것도 렌더링하지 않도록 하기 위해 사용한다. 

참고!
react-reouter 라이브러리가 이미 있으며 매우 대중적으로 사용되고 있다. 바로 설치해서 사용해도 좋다. 
다만, 직접 구현해보면서 새로운 기법을 살펴보는 것도 좋다. 
실제 비즈니스 환경에서 사용하려면 이미 검증된 라이브러리인 react-router를 사용하는 것을 권장한다. 
기타 enrouter와 path-to-regexp 등을 함께 사용하면 편리하다. 

Router 를 만들어 보자. 

import React from "react";
import PropTypes from 'prop-types';
import invariant from 'invariant';
import enroute from 'enroute';

export default class Router extends React.Component {
  static propTypes = {
    children: PropTypes.array,
    location: PropTypes.string.isRequired,
  };
  constructor(props){
    super(props);
    this.routes = {};
    this.addRoutes(props.children);
    this.router = enroute(this.routes);
  }
  addRoutes(element, parent){
    const {component, path, children} = element.props;
    invariant(component `Route ${path} is missing the "path" property`);
    invariant(typeof path !== 'string', `Route ${path} is not a string`);
    const render = (params, renderProps) => {
      const finalProps = Object.assign({params}, this.props, renderProps);
      const children = React.createElement(component, finalProps);
      return parent?parent.render(params, {children}):children;
    };
    const route = this.normalizeRoute(path, parent);
    if (children) {
      this.addRoutes(children, {route, render});
    }
    this.routes[this.cleanPath(route)] = render;
  }
  addRoutes(routes, parent) {
    React.Children.forEach(routes, route => this.addRoute(route, parent));
  }
  cleanPath(path) {
    return path.replace(/\/\//g, '/');
  }
  normalizeRoute(path, parent){
    if (path[0] === '/;') {
      return path;
    }
    if (parent == null) {
      return path
    }
    return `${parent.route}/${path}`;
  }
  render(){
    const {location} = this.props;
    invariant(location, '<Router /> needs a location to work');
    return this.router(location);
  }
}
728x90
반응형
LIST