前端路由

什么是路由?

路由最一开始是在后端中出现的,我们在访问某些网站的时候经常会见到类似于这样的url: http://www.xxx.com/xxx.php 或者 http://www.xxx.com/xxx.html,这就是SSR(Server Side Render)服务端渲染,通过后端直接渲染前端页面。这里客户端会发向服务端发请求,服务端解析url,根据url将需要的资源返回给前端,路由其实就是前端和服务端的一种交互方式,相当于服务端针对于不同urlswitch case,每一类case对应一个controller

前端路由

上面介绍了传统意义上的路由,用户在页面上点击的时候触发页面请求,服务端将对应的页面返回回来,这样存在一个问题是,整个页面会重新加载,会产生无意义的白屏页面,用户体验不好。在用户点击访问网站或者Web App上的另一个页面的时候,这个时候我们可能仅仅需要的是更新部分视图就可以了,而没有必要去重新加载整个页面,避免无意义的白屏;这个时候前端路由就出现了,页面的跳转和渲染放在前端,后端提供API获取数据即可。

前端路由的实现原理

对于前端路由有两种实现方式,一种是通过hashchanged;另一种是使用HTML5提供的history api来做前端路由的处理。 对于hasnchange的实现方式,我们需要监听hashchange事件,在浏览器的url中改变hash不会导致页面发请求,我们在hashchange的事件回调中处理页面中DOM的刷新。 hashchange html5推出pushstatereplacestate之后,就可以通过这两个api来改变url并且不发请求,而且相比于使用hash,页面的url更加美观,但是通过pushstate方式改变了url,如果这个时候刷新了页面,服务端会收到请求,这个时候后端没有对应的controller来处理,所以在pushstate的实现方式下,后端最好对于没有路由的url直接返回到主页面。

如何实现一个简单的前端路由

这里我们来尝试实现一个简单的前端路由,主要的思想就是侦测url的变化,解析url,调用handler,整个路由是一个单例。

const Route = {
    routes: [],
    config: function(options) {
        this.root = options.root || '/';
        if (options.mode === 'history' && typeof history.pushState === 'function') {
            this.mode = 'history';
        } else {
            this.mode = 'hash';
        }
        return this;
    }
}

基于浏览器的两种实现方式,我们实现路由的时候,提供mode的设置,默认是’hash’的实现,这里我们配置了根路径,这里主要是处理在pushstate的实现中,pushstate导致无法获取到url改变的部分的问题: 19 4 13 01 19 4 13 02 如上图,我们原本的页面在https://github.com下,在第一次pushstate的时候,没有问题,但是在第二次pushstate的时候,和我们期待的url并不符合,这会导致的问题是,我们在解析url change的部分的时候,会得到错误的结果,所以这里我们指定好根目录,在解析的时候将url中根路径剔除掉。

  getFragement: function () {
    let fragement = '';
    if (this.mode === 'history') {
      fragement = location.pathname + location.search;
      fragement = this.clearSlash(fragement);
      fragement = fragement.replace('/\?(.*)$/', '');  // replace the query string
      fragement = this.root !== '/' ? fragement.replace(this.root, '') : fragement;
    } else {
      fragement = location.hash.slice(1);
    }
    return fragement;
  }

上面的代码就是去解析出’url’改变的部分;在pushstate的实现中,存在一个问题是,我们在url跳转的时候,是调用pushstate来改变url,但是这并不能触发popstate的事件,所以这里我们需要去listen url的变化:

  startTimer: function(fn, ms) {
    const self = this;
    const internalFn = function () {
      fn();
      self.timer = setTimeout(internalFn, ms);
    }
    this.timer = setTimeout(internalFn, ms);
  },
  listen: function () {
    let current = this.getFragement();
    let self = this;
    const fn = function() {
      if (current !== self.getFragement()) {
        current = self.getFragement();
        self.check(current);
      }
    };
    this.startTimer(fn, 50);
    return this;
  },

完整的代码实现请查看: FERoute

Refrence


兴趣遍地都是,坚持和持之以恒才是稀缺的


Written by@wang yao
I explain with words and code.

GitHub