71

怎么理解前端router? 当然是自己实现一个啦!

 6 years ago
source link: https://segmentfault.com/a/1190000012649983?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

怎么理解前端router? 当然是自己实现一个啦!

spa流行的今天不少同学会把前端路由跟后端路由弄混, 莫名其妙的怎么页面404啦之类'奇怪'的问题, 其实这就是没弄清楚前端路由和后端路由的原因(当然你用hash当我没说).

本文所有前端路由都是spa的情况下, 不存在后端渲染好变量的情况

首先我们看看前后端路由在浏览器中是怎么工作的, 上图:

后端控制的路由:

image

我们可以知道后端其实返回的是html字符串, 也就是dom节点不出意外的话是确认的. 不管你请求多少次, 结果都是确定的(get 幂等). 所以也就不存在404的情况

前端控制的路由:

image

如果是spa的话, 我们可以知道不管你请求那个页面, 在后端处理好的情况下后端都会返回一个html文件(所谓单页的由来), 静态资源当然也是类似的. 那么我们可能有点疑问, 比如一个个人主页, 如果只返回一个html文件的话, 怎么得到不同的用户资料呢, 答案就是前端路由(大部分情况, 不排除本地存储?), js根据不同的路由再向服务器请求相关资料, 也就是说其实第一次服务端渲染我们的页面是空的, 后期ajax请求. 所以我们看到很多单页页面打开了首先要loading一会. 就是在向服务器请求渲染页面.

后端路由我们暂且不去管它, 我们看看是怎么实现的:

在非hash的情况下, 前端路由的实现基础是window.history, 当然我们不用去管它的兼容性了, 反正现在大部分浏览器能用就是了:

image

history有个重要的方法就是pushState, 其它的方法暂时用不到不提, 它的作用呢就是改变浏览器地址栏里的地址, 以及在历史纪录里加上一条, 除此以外没啥别的副作用了, 比如:

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

上面的代码只会跳到一个 bar.html的地址, 但是页面本身并未跳转, 我们不是来讲history对象本身的, 有兴趣可以自行翻看mdn.

其实参考后端对路由的控制, 我们大略可以想像一个前端路由所具有的功能:

  1. 对路由做出响应
  2. 一些事件, 比如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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK