怎麼理解前端路由? 固然是本身實現一個啦!

spa流行的今天很多同窗會把前端路由跟後端路由弄混, 莫名其妙的怎麼頁面404啦之類'奇怪'的問題, 其實這就是沒弄清楚前端路由和後端路由的緣由(固然你用hash當我沒說).css

本文全部前端路由都是spa的狀況下, 不存在後端渲染好變量的狀況html

原理

首先咱們看看先後端路由在瀏覽器中是怎麼工做的, 上圖:前端

後端控制的路由:jquery

image

咱們能夠知道後端其實返回的是html字符串, 也就是dom節點不出意外的話是確認的. 無論你請求多少次, 結果都是肯定的(get 冪等). 因此也就不存在404的狀況ajax

前端控制的路由:bootstrap

image

若是是spa的話, 咱們能夠知道無論你請求那個頁面, 在後端處理好的狀況下後端都會返回一個html文件(所謂單頁的由來), 靜態資源固然也是相似的. 那麼咱們可能有點疑問, 好比一個我的主頁, 若是隻返回一個html文件的話, 怎麼獲得不一樣的用戶資料呢, 答案就是前端路由(大部分狀況, 不排除本地存儲😂), js根據不一樣的路由再向服務器請求相關資料, 也就是說其實第一次服務端渲染咱們的頁面是空的, 後期ajax請求. 因此咱們看到不少單頁頁面打開了首先要loading一會. 就是在向服務器請求渲染頁面.後端

實現

後端路由咱們暫且不去管它, 咱們看看是怎麼實現的:瀏覽器

在非hash的狀況下, 前端路由的實現基礎是window.history, 固然咱們不用去管它的兼容性了, 反正如今大部分瀏覽器能用就是了: 服務器

image

history有個重要的方法就是pushState, 其它的方法暫時用不到不提, 它的做用呢就是改變瀏覽器地址欄裏的地址, 以及在歷史紀錄里加上一條, 除此之外沒啥別的反作用了, 好比:app

var stateObj = { foo: "bar" };
history.pushState(stateObj, "page 2", "bar.html");
複製代碼

上面的代碼只會跳到一個 bar.html的地址, 可是頁面自己並未跳轉, 咱們不是來說history對象自己的, 有興趣能夠自行翻看mdn.

其實參考後端對路由的控制, 咱們大略能夠想像一個前端路由所具備的功能:

  1. 對路由作出響應
  2. 渲染
  3. 一些事件, 好比beforeChange之類的

固然咱們如今一切從簡, 上面那些說清楚了起實現無非就是苦力了, 先給你們看看效果吧:

2

仍是有點意思的吧.

下面是html代碼:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
  <nav class="navbar navbar-default nav-static-top">
    <div class="container-fluid">
      <!-- Brand and toggle get grouped for better mobile display -->
      <div class="navbar-header">
        <a class="navbar-brand" href="#">LOGO</a>
      </div>

      <!-- Collect the nav links, forms, and other content for toggling -->
      <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
        <ul class="nav navbar-nav">
          <li><a href="/1" data-role="custom-history">地址1 <span class="sr-only">(current)</span></a></li>
          <li><a href="/2" data-role="custom-history">地址2</a></li>
        </ul>
      </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
  </nav>
  <div id="app" class="container">
    <div class="panel panel-default">
      <div class="panel-heading">Panel heading without title</div>
      <div class="panel-body">
        Panel content
      </div>
    </div>

  </div>

  <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
  <script src="./route.js"></script>
  <script src="./index.js"></script>
</body>
</html>
複製代碼

index.js

const $routeController = $('a[data-role=custom-history]')

const app = new Route()

app.set('/1', function () {
  $('#app').html('1')
})

app.set('/2', function () {
  $('#app').html(2)
})
複製代碼

route.js

class Route {
  constructor () {
    this.urls = []
    this.handles = {}
    window.addEventListener('popstate', (e) => {
      const state = e.state || {}
      const url = state.url || null

      if (url) {
        this.refresh(url)
      }
    })

    const $routeController = $('a[data-role=custom-history]')

    $routeController.on('click', e => {
      e.preventDefault()
      const link = $(e.target).attr('href')
      history.pushState({ url: link }, '', link)
      this.refresh(link)
    })
  }

  set (route, handle) {
    if (this.urls.indexOf(route) === -1) {
      this.urls.push(route)
      this.handles[route] = handle
    }
  }

  refresh (route) {
    if (this.urls.indexOf(route) === -1) throw new Error('請不要這樣調用, 路由表中不存在!')
    this.handles[route]()
  }
}
複製代碼

按個人本意是不想在一篇文章裏貼這麼多代碼的, 可是由於也不能夠直接嵌入jsbin之類的, 方便你們試試看效果, 就放進來把, 由於代碼比較簡單, 並且深度綁定到了dom上, 就不要嘲笑啦!

image
相關文章
相關標籤/搜索