使用 React-Router 建立单页应用

最近业余时间在学习 React,配合 Redux 和 React-Router 正在不紧不慢地开发一个小工具moviemaster,用于管理硬盘中的电影剧集。在单页应用开发中,redux 并非必须的,因此今天只讲讲 前端的路由系统以及 React-Router的简单使用。html

什么是路由

如下来自维基百科::前端

路由(routing)就是经过互联的网络把信息从源地址传输到目的地址的活动。路由发生在OSI网络参考模型中的第三层即网路层。
路由引导分组转送,通过一些中间的节点后,到它们最后的目的地。react

这是网络工程中的术语,对你们而言,最熟悉的应该就是家里的路由器。路由是指路由器从一个接口上收到数据包,根据数据包的目的地址进行定向并转发到另外一个接口的过程。放在 Web 上来讲,url 就像是路由器中的路由表,每一个 url 对应不一样的页面或者内容,就像路由表中的的 IP 对应不一样的网络同样。git

先来看一下熟悉的套路:github

image_1b0a1gh7ge4u1g9l14mm7v41me9a.png

在传统的网页应用架构中,客户端只是一个展现层,经过 url 访问服务端,服务端则根据本身的“路由表”将对应的页面分发给客户端。可是在这种模式下,ajax 异步加载的内容是没法经过url 记录的。不管你在页面上操做了多少,异步请求了多少数据,在每次从新访问同一个 url 时,服务端返回给客户端的内容都是如出一辙。ajax

image_1b0a24tg94le1p03qa76br1apfg.png

若是前端有本身专属的“路由表”来分发页面上不一样的状态,那不就好了?redux

Hash 和 pushState

据我所知,目前有两种方式能够构建出前端的路由系统:url 中的#和 HTML5中的 history API。其原理以下:浏览器

  1. 阻止标签的默认跳转动做。bash

  2. ajax或者 Fetch 请求内容。网络

  3. 将返回的内容添加到页面中。

  4. 使用 hash 或者 pushState 修改 url。

经典的 Hash

#表明网页中的一个位置。后面接着的字符,就是该位置的标识符。好比,

https://zhanglun.github.io/index.html#body

就表明网页 index.html 的 body 位置。浏览器读取这个 URL 后,会自动将body位置滚动至可视区域。标识符的指定有两个方法。

  1. 使用锚点

<a name="body"></a>
  1. 使用id属性

<div id="body" >

#是用来指向文档的内容,属于浏览器的行为,与服务端无关,在 HTTP请求中也不会携带 #及其后面的内容,对于服务端而言 http://www.baidu.comhttp://www.baidu.com#action=fuckbaidu 返回给客户端的都是前者所分发的内容,可是在浏览器中能够经过 Window 对象上的 location.hash 进行操做。所以,在浏览器中能够经过 hash 来记录页面的状态,构建“路由表”。当页面状态发生变化时,hash 相应变化,从新加载时又能够经过 url 中携带的 hash 直接将页面设置到对应的状态。

好比:

http://www.example.com/
http://www.examplt.com/#edit
http://www.examplt.com/#settings
  1. 访问/时,呈现主页。

  2. 点击页面上的Edit按钮,页面呈现编辑对应的内容。经过 url 直接访问时,检查 hash 是否和 edit 匹配,若是匹配执行加载编辑内容的代码

  3. 点击页面上的Settings按钮,页面呈现设置对应的内容。经过 url 直接访问时,检查 hash 是否和 settings 匹配,若是匹配执行加载编辑内容的代码。

如下是伪代码:

function hashHandler () {
    let key = location.hash.slice(1);
    switch(key) {
      case 'edit':
        renderEditPanel();
        break;
      case 'settings':
        renderSettings();
        break;
       default:
        break;
    }
  }
  window.onload = () => {
    hashHandler();
  }
  window.onhashchange = () => {
    hashHandler();
  }

HTML5 中的 pushState

pushState是 History API中的一个方法,其文档能够看这里 MDN History。它的功能简单的说就是:修改 url,添加历史记录。好比/blogssettings对应的是两个页面,若是只是在页面上点击标签切换,须要作的操做只有:发送请求修改页面内容和调用 pushState 方法修改 url。问题来了,对于前端而言须要将其视为同一个页面,但实际上这两个 url 对于服务端来讲是两个不一样的请求,因此这里须要服务端的配合。

个人作法是:对应的url 返回的都是同一个页面,而后浏览器接受以后检查前端定义路由系统,执行响应的代码。这个方法可能会形成页面平白添加一个短暂的延迟,不过影响不是很大。

React-Router的使用

目前来讲,任何一个路由系统库或者框架,虽然说是写法不一,可是都是在上述两种方式的基础上实现的。让我以为耳目一新的是:使用路由嵌套的概念来定义 view 的嵌套集合,当一个给定的 URL 被调用时,整个集合中(命中的部分)都会被渲染。

import React from 'react';
import { render } from 'react-dom';
import { Router, Route, IndexRoute, hashHistory } from 'react-router';

import App from './containers/App';
import MovieContainer from './containers/Movies';
import Detail from './containers/Detail';


let rootElement = document.getElementById('app');
render(
  <Router>
    <Route path="/" component={App}>
      <Route path="about" component={About} />
      <Route path="inbox" component={Inbox}>
        <Route path="messages/:id" component={Message} />
      </Route>
    </Route>
  </Router>,
rootElement);

在入口文件中,引入 React-Router,以组件的形式在 render 中使用,上述代码配置结果以下:

URL 组件
/ App
/about App -> About
/inbox App -> Inbox
/inbox/messages/:id App -> Inbox -> Message

在路由中,组件对应设置的子组件能够经过 this.props.children 渲染在父组件中

class App extend Component {
  constructor(props) {
    super(props)
  }
  render() {
    <div id="app">
      <h1>Hello, world!</h1>
      {this.props.children}
    </div>
  }
}

当 URL 为 / 时, App 中并无渲染任何的组件,render 中的 this.props.children 仍是 undefined。此时可使用 IndexRoute 来设置一个默认页面。

render(
  <Router>
    <Route path="/" component={App}>
      {/* 当 url 为/时渲染 Welcome */}
      <IndexRoute component={Welcome} />
      <Route path="about" component={About} />
      <Route path="inbox" component={Inbox}>
        <Route path="messages/:id" component={Message} />
      </Route>
    </Route>
  </Router>,
rootElement);
URL 组件
/ App -> Welcome
/about App -> About
/inbox App -> Inbox
/inbox/messages/:id App -> Inbox -> Message

看一下这一段代码

<Route path="posts" component={Post}>
  <Route path="users/:userid" component={User}>
    <Route path="messages/:messageid" component={Message} />
  </Route>
</Route>

此时匹配的路由分别是:/posts/posts/usres/:userid/posts/users/:userid/messages/:messageid,能够看出,嵌套的<Route>所匹配的 url是包裹着它的 <Route>的 path “之和”。可是问题又来了,嵌套的好处在于路由之间结构清晰直观,可是也会致使 url 的不美观,试想/posts/users/:userid/messages/:messageid这么长的路由也是着实让人心累。React-Router 的配置提供了一个选择:将 Route 的 path 设置成绝对路径。同时可使用<Redirect/> 将修改成绝对路径的路由重定向到以前的设置

<Route path="posts" component={Inbox}>
  <Route path="/users/:userid" component={Message}>
    <Route path="/messages/:messageid" component={Message} />
  </Route>
</Route>
URL 组件
/posts App -> Post
/user/:userid App -> Post -> User
/messages/:messageid App -> Post -> User ->Message

基础的配置完成以后,经过 <Link>自动或者经过browserHistoryhashHistory手动执行路由的跳转。

相关文章
相关标签/搜索