React服务端渲染之路03——路由

全部源代码、文档和图片都在 github 的仓库里,点击进入仓库javascript

相关阅读

1. 路由

  • 在页面上有了内容以后,咱们还须要进行路由的跳转
  • 下载路由依赖包,react-router-dom,npm i react-router-dom -S
  • 这里暂时先不介绍多级路由,多级路由放在后边介绍,如今咱们只介绍一级路由

1.1 路由介绍

  • 咱们在使用客户端渲染的时候,页面跳转路由是前端控制的,主要有两种模式,一种是 history 模式,另一种是 hash 模式。
  • 不管是哪一种模式,均可以进行路由跳转操做,惟一的不一样是 history 模式须要后端控制 404 页面,而 hash 模式不须要
  • 在服务端渲染,咱们须要的是静态路由(static router),与客户端的路由模式是不同的
  • 路由的控制,不只仅是在服务端,客户端也须要进行一样的路由规则,那么咱们就能够写一份路由,供客户端和服务端一块儿使用

1.2 路由页面组件

  • 路由跳转,至少须要两个页面级组件,因此咱们能够先建立一个 News 页面组件,containers/News/index.js
  • 以前咱们已经有了一个 Home 页面级组件,如今有两个组件,如今页面咱们已经准备好了
  • containers/News/index.js
// containers/News/index.js
import React, { Component } from 'react';

class News extends Component {
  render() {
    return (
      <div>
        <h1>News Page</h1>
      </div>
    );
  }
}

export default News;

1.3 路由文件

  • 咱们须要导出一个路由组件,组件内部是 Route
  • 导出的路由组件,须要做为子组件,才能在页面上渲染
  • 如今咱们先采用这种模式,后期咱们会用另一种模式,可是不管是哪一种模式,实质上是同样的
  • 注: <></> 标签和 <Fragment><Fragment/> 标签是同样的,都是一个聚合子元素的一个标签,不增长真实的 DOM 节点
  • src/routes.js
// src/routes.js
import React from 'react';
import { Route } from 'react-router-dom';
import Home from './containers/Home';
import News from './containers/News';

export default (
  <>
    <Route path='/' exact component={Home}/>
    <Route path='/news' component={News}/>
  </>
);

1.4 客户端路由

  • 客户端路由比较简单,直接把路由组件做为子组件传递就能够
  • 在这里直接把路由放在这里,其实是把路由文件里的多个 Route 放在这里,若是咱们把路由里的 Route 放在这里,咱们会发现其实是同样的,没有什么区别
import React from 'react'
import { hydrate } from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import routes from '../routes';

hydrate(
  <BrowserRouter>
    {routes}
  </BrowserRouter>, window.root);

1.5 服务端路由

  • 服务端路由相对复杂一些,须要使用无状态路由,也就是静态路由 StaticRouter

,由于服务端并不知道请求是经过什么模式,并且服务端也无需知道,服务端只须要根据客户端发送的请求,作相应的处理便可css

  • StaticRouter 里须要传递两个属性,一个是 context,一个是 location
  • context 主要是用来给组件传递数据信息,这个属性能够在客户端和服务端互相传递参数,好比 css 样式的参数
  • location 主要是用来接收请求路径信息,好比 pathname,search,hash 等
  • 修改 src/server/index.js
import routes from '../routes';

app.get('*', (req, res) => {
  let context = {};

  let domContent = renderToString(
    <StaticRouter context={context} location={req.path}>
      {routes}
    </StaticRouter>
  );

  // html 的内容不改变
  res.send(html);
});
  • 从代码中咱们能够看到,咱们移除了 Home 组件,引入了 routes 组件,由于 routes 已经能够匹配到 Home 和 News 组件
  • 添加了 StaticRouter 组件,传入了一个 context 值,把 express 的 req.path 传递给了 location 属性,这样服务端就能够知道客户端的路由地址
  • 修改了 app.get('/')app.get('*'),由于咱们服务端不只仅是接收同一个路由地址,咱们还有 /news 路由,因此咱们要把根路径匹配修改成全局匹配。若是不修改成全局匹配呢,也能够,只不过咱们若是有 100 个路由,咱们就要写 100 个 app.get('/xxx'),若是你愿意的话,也是能够的
  • 原理就是html

    • 客户端经过路由修改了请求的地址,服务端接收相应,在 StaticRouter 中咱们获取到了客户端请求的路由地址
    • routes 就开始进行匹配,若是匹配到了,那么就把匹配到的路由组件进行渲染成 HTML 字符串,发送给客户端。
    • 若是没有匹配到呢,假如咱们访问了 http://localhost:3000/hello 路由,那么咱们的 routes 是匹配不到的,既然匹配不到,那么 renderToString 渲染的结果就是空,domContent 做为空值插入 HTML 模板,获得的就是一个空白页面
  • 此时,咱们就能够在经过不一样的 url,访问到不一样的页面,查看网页源代码,能够看到不一样 url 对应的源代码就是对应组件里的代码

1.5 页面效果与页面源代码

  • 首页面效果

首页面效果

  • 首页面源代码

首页面源代码

  • news 页面效果

news 页面效果

  • news 页面源代码

news 页面源代码

2. 路由跳转

  • 咱们已经实现了路由的切换,可是咱们是经过输入 url 来完成路由切换的,显然咱们应该经过点击实现跳转,因此咱们使用 Link 实现路由跳转
  • 咱们建立一个 Header 导航,在 Header 导航中进行点击路由,实现页面的切换
  • 为了样式更加美观,咱们引入 bootstrap@3.3.7,这里是采用了 又拍云(bootcdn) 的 bootstrap 资源,咱们也能够把 bootstrap 放入 public 文件夹下,像引入 client.js 同样引入 bootstrap.css,效果是同样的

2.1 Header 组件

  • Header 组件,/components/Header/index.js
  • 使用 bootstrap 的导航样式
  • /components/Header/index.js
// /components/Header/index.js
import React, { Component } from 'react';
import { Link } from 'react-router-dom';

class Header extends Component {
  render() {
    return (
      <nav className="navbar navbar-inverse navbar-fixed-top">
        <div className="container-fluid">
          <div className="navbar-header">
            <a className="navbar-brand">REACT-SSR</a>
          </div>
          <div>
            <ul className="nav navbar-nav">
              <li><Link to="/">首页</Link></li>
              <li><Link to="/news">新闻</Link></li>
            </ul>
          </div>
        </div>
      </nav>
    );
  }
}

export default Header;

2.2 把 Header 组件进行同构

  • 这里的同构就很简单,直接把 Header 组件放入 BrowserRouter 和 StaticRouter 中,而后稍微进行一下样式的调整,就能够了
  • 客户端同构,修改 src/client/index.js
// src/client/index.js
import React from 'react'
import { hydrate } from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import routes from '../routes';
import Header from '../components/Header';

hydrate(
  <BrowserRouter>
    <>
      <Header/>
      <div className="container" style={{marginTop: 70}}>
        {routes}
      </div>
    </>
  </BrowserRouter>, window.root);
  • 服务端同构,修改 src/server/index.js
// src/server/index.js
import Header from '../compoents/Header';

let domContent = renderToString(
  <StaticRouter context={context} location={req.path}>
    <>
      <Header/>
      <div className="container" style={{marginTop: 70}}>
        {routes}
      </div>
    </>
  </StaticRouter>
);

相关阅读

相关文章
相关标签/搜索