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>
);

相關閱讀

相關文章
相關標籤/搜索