路由

hashchange

history API javascript标准参考教程(alpha) 前端路由简介 https://juejin.im/post/5ac61da66fb9a028c71eae1b

后端路由

出现SPA大宝剑前,一般路由指的就是后端路由,服务器根据不同的请求返回不同的资源。

一般就是浏览器发出请求(url变化)--> 服务器监听80(443)端口的请求,解析url --> 根据服务器的路由配置,返回相应的信息(html, json等)--> 浏览器根据content-type决定如何解析。

// 这里有段koa的路由
module.exports = function(){
    var router = new Router({
    prefix: '/api'
  })

  router.post('/u/signup', App.hasBody, User.signup)
  router.post('/u/update', App.hasBody, App.hasToken, User.update)

  router.get('/test/user/users',User.users)
  router.post('/test/user/add',User.addUser)
  router.post('/test/user/delete',User.deleteUser)

  return router
}

前端路由

匹配不同的URL,进行解析,动态渲染出部分区域的HTML。

hash路由

上古WEB自从有个ajax ,整个WEB世界就变得越来越有意思了,ajax 是可以在不刷新浏览器的情况下异步请求交互,之后出现的SPA,更是可以完成无刷新的情况下实现路由跳转。

www.gebilaowang.com/#/helloworld

原理 url中 #后面的hash值的变化不会像服务器发出请求,也就不会刷新页面。当URL的片段标识符更改时,还会触发hashchange事件 (跟在#符号后面的URL部分,包括#符号)。我们监听浏览器的hashchange时间来做相应的路由操作。当我们刷新页面的时候,也不会像浏览器发请求。

//mini hashhistory路由
//
 class hashRouter {
    constructor() {
        //存储路由
        this.routes = {}
        this.currentUrl = ''
        this.refresh = this.refresh.bind(this);
        window.addEventListener('load', this.refresh, false);
        window.addEventListener('hashchange', this.refresh, false);
    }
    // 存储路由的hash以及对应的callback,
    route(path, callback = function(){}) {
        this.routes[path] = callback
    }
    //刷新
    refresh() {
        this.currentUrl = location.hash.slice(1) || '/'
        // 执行当前路由对应的callback
        this.routes[currentUrl]()
    }

 }

hash路由实现

history路由

HTML5标准中引入了 history.pushState() 和 history.replaceState() 方法,它们分别可以添加和修改历史记录条目。我们可以通过这个来实现另一种方式的前端路由。 history路由没有 #,更加美观,而且因为没有#, 用户刷新页面之类的操作时,还是会 像服务器发送请求。(hash路由并不会) 假如RD没有响应的配置,经常会出现404,所以这种方案需要服务器的支持, 所有的路由都重定向到根模板

  1. history.pushState()

假如当前是 www.gblw.com/1.html

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

执行之后浏览器显示的是 www.gblw.com/2.html,但是并不会跳转到2.html,也不是检查2.html是否存在。只是成为浏览器历史中的一个记录。

window.history.pushState(state, title, url)

- state:一个与添加的记录相关联的状态对象,主要用于popstate事件。该事件触发时,该对象会传入回调函数。也就是说,浏览器会将这个对象序列化以后保留在本地,重新载入这个页面的时候,可以拿到这个对象。如果不需要这个对象,此处可以填null。
- title:新页面的标题。但是,现在所有浏览器都忽视这个参数,所以这里可以填空字符串。
- url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。

pushState()方法不会触发页面刷新,只是导致 History 对象发生变化,地址栏会有反应。

  1. history.replaceState() 修改history当前的记录,其他与pushState一样。

  2. popstate事件

window.addEventListener('popstate', function(event) {
  console.log('location: ' + document.location);
  console.log('state: ' + JSON.stringify(event.state));
});

每当同一个文档的浏览历史(即history对象)出现变化时,就会触发popstate事件。

注意 - 仅仅调用pushState()方法或replaceState()方法 ,并不会触发该事件,只有用户 点击浏览器倒退按钮和前进按钮,或者使用 JavaScript 调用 **History.back()、History.forward()、History.go()**方法时才会触发。 - 该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件也不会触发。 - 页面第一次加载的时候,浏览器不会触发popstate事件。 - popstate事件的回调函数的参数event对象。他的state属性执行pushState和replaceState方法为当前URL所提供的状态对象,(即两个方法的第一个参数),也可以通过history对象读取。

// mini history路由
 class HistoryRouter {
    constructor() {
        // 存储路由
        this.routes = {}
        // 初始化的时候监听popstate事件
        this._bindPopState()
    }
    // 初始化路由
    // 页面第一次加载的时候,浏览器不会触发popstate事件,我们需要init时replaceState
    init(path) {
        history.replaceState({path}, null, path)
        this.routes[path] && this.routes[path]()
    }
    // 路径和回调对应
    route(path, callback = function(){}) {
        this.routes[path] = callback
    }
    // 触发路由对应的回调
    go(path) {
        history.pushState({path}, null, path)
        this.routes[path] && this.routes[path]()
    }
    //监听popstate事件
    _bindPopState() {
        window.addEventListener('popstate', e => {
        // 当点击浏览器倒退按钮和前进按钮或者 执行history.go/forward/back等方法时,
        // 拿到对应路由下存储的状态,执行其回调
            const path = e.state && e.state.path
            this.routes[path] && this.routes[path]()
        })
    }

 }

原理很简单:就是通过 pushState,replaceState API,将对应的状态以及相应的回调存储下来。在用popstate来做相应的配合。 当匹配到相应路由的状态的时候,执行相应的回调。

  <!DOCTYPE html>
 <html>
 <head>
     <title></title>
 </head>
 <body>
   <ul>
      <li><a href="/">home </a></li>
      <li><a href="/gblw">gblw </a></li>
      <li><a href="/gbll">gbll </a></li>
  </ul>
  <div id='body'></div>
  <script>
      let body = document.querySelector("#body")
      let router = new HistoryRouter()
      // 注册路由
      router.route('/',e => {
        body.innerHTML = '<div>home</div>'
      })
      router.route('/gblw',e => {
        body.innerHTML = '<div>gblw</div>'
      })
      router.route('/gbll', e => {
        body.innerHTML = '<div>gbll</div>'
      })
      // 初始化路由 要在注册路由之后
      // router.init(location.pathname)
      router.init('/')
    document.querySelector("ul").addEventListener('click', e => {
      if (e.target.tagName === 'A') {
        e.preventDefault();
        router.go(e.target.getAttribute('href'));
      }
    },false);
  </script>
 </body>
 </html>

minihistory

了解路由的前端路由的原理,能让我们更好地理解现代前端开发中的路由控制,还能让我们更好地跟RD沟(che)通(pi), 例如

  • 58有个生成url二维码功能,所以需要我们对雇主盛典项目从hash路由改进到history,需要跟RD沟通怎么去配置模板...
  • 春运项目3.1下线,需要将将活动主会场链接到分会场, RD对 /index请求做了拦截,但是 分会场有个返回主会场的按钮,点击返回到index还是会跳到主会场,因为第一次请求分会场/branch,RD会去做重定向 到我们的index模板,当我们从branch跳转到index的时候,其实并没有想后端去做请求,也就没办法去控制,这部分路由的控制权在我们手里。
Last Updated: 3/20/2019, 10:51:24 AM