Vue 路由

63 阅读2分钟

什么是路由

  • 路由的概念是伴随着SPA 出现的,在此之前,页面的跳转是通过服务器控制的
    • 传统的页面跳转是通过前端向后台发送请求;
    • 后台通过模版引擎渲染,将一个新的html界面再返回给前台;
    • 比如页面跳转时:
      • a 标签的 href
      • form 表单
      • location.href
    • 在 SPA 出现之后,前端可以自由地控制组件的渲染,来模拟页面的跳转
      • hash路由和history路由

总结:

  • 传统的路由是根据url访问相关的controller,进行数据资源和模板引擎的拼接,返回给前端;
  • 前端路由使用js根据url返回对应的组件加载;
    • 告诉服务端不要处理路由
    • 自己对url进行处理
    • 根据url的规则匹配加载组件

hash 路由和history 路由的区别

  • hash路由一般会带一个#号,不够美观;history 路由不存在这个问题
  • 默认hash路由是不会向浏览器发起请求(最早用于做锚点);history路由中的go / back / forward以及浏览器中的前进后退按钮,一般都会向服务器发起请求;(pushState / replaceState 不会)
  • history 路由在部署的时候,需要设置只渲染首页
#单个服务器的部署
location / {
    try_files uri $uri /xx/xxxx/xxx/index.html
}

#存在代理的情况

locatoin / {
    rewrite ^ /file/xx/xxxx/index.html break;
    proxy_pass https://www.xxx.cdn.com;
    
}
  • 基于此hash路由是不支持SSR的;但是history 路由是可以的;
  • hash 路由的监听一般是用onHashChange 事件,history 路由的监听onPopState进行监听

histroy 路由

history 本质上是一个BOM API,里面有go / forward / back 三个API,以及pushState / replaceState;

  • pushState / replaceState 都不会触发popState事件;
  • popState 什么时候触发?
    • 点击浏览器前进后退按钮;
    • go / forward / back;

实现简单的router

    <!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="container">

    <a href="./">首页</a>
    <a href="./about">关于我们</a>
    <a href="./user">用户列表</a>
  </div>
  <div id="context"></div>
  <script>

    class BaseRouter {
      constructor() {
        this.routes = {};
        this._bindPopState();
      };
      route(path, cb) {
        this.routes[path] = cb || function () { }
      };

      go(path) {
        window.history.pushState({ path }, null, path);
        const cb = this.routes[path];
        if (cb) {
          cb();
        }
      }

      init(path) {
        window.history.replaceState({ path }, null, path);
        const cb = this.routes[path];
        if (cb) cb();
      }
      //保证onPopState 在触发的时候页面也会进行更新
      _bindPopState() {
        window.addEventListener('popState', event => {
          const path = event.state && event.state.path;
          this.routes[path] && this.route[path]();

        })
      }
    }
    const Route = new BaseRouter();
    Route.route('./about', () => changeText('关于我们页面'))
    Route.route('./user', () => changeText('用户列表页'))
    Route.route('./', () => changeText('首页页面'))

    function changeText(arg) {
      document.getElementById('context').innerHTML = arg;
    }

    document.getElementById('container').addEventListener('click', e => {

      if (e.target.tagName === 'A') {
        e.preventDefault();
        Route.go(e.target.getAttribute('href'));
      }
    })
  </script>
</body>

</html>

hash

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="container">

    <a href="#">首页</a>
    <a href="#about">关于我们</a>
    <a href="#user">用户列表</a>
  </div>
  <div id="context"></div>
  <script>

    class BaseRouter {
      constructor() {
        this.routes = {};
        // this._bindPopState();
        // this.init();

        this.refresh = this.refresh.bind(this);
        window.addEventListener('load', this.refresh)
        window.addEventListener('hashchange',this.refresh);
      };
      //收集路由
      route(path, cb) {
        this.routes[path] = cb || function () { }
      };
      //访问路由,执行函数
      go(path) {
        window.history.pushState({ path }, null, path);
        const cb = this.routes[path];
        if (cb) {
          cb();
        }
      }
      refresh(){
        const path = `/${window.location.hash.slice(1) || ''}`
        
        this.routes[path]();


      }

      init(path) {
        window.history.replaceState({ path }, null, path);
        const cb = this.routes[path];
        if (cb) cb();
      }
      //保证onPopState 在触发的时候页面也会进行更新
      _bindPopState() {
        window.addEventListener('popState', event => {
          const path = event.state && event.state.path;
          this.routes[path] && this.route[path]();

        })
      }
    }
    const Route = new BaseRouter();
    Route.route('/about', () => changeText('关于我们页面'))
    Route.route('/user', () => changeText('用户列表页'))
    Route.route('/', () => changeText('首页页面'))

    function changeText(arg) {
      document.getElementById('context').innerHTML = arg;
    }

    // document.getElementById('container').addEventListener('click', e => {
    //   if (e.target.tagName === 'A') {
    //     e.preventDefault();
    //     Route.go(e.target.getAttribute('href'));
    //   }
    // })
  </script>
</body>

</html>

路由守卫的触发流程

  1. [组件] - 前一个组件 beforeRouterLeave,
  2. [全局] - router.beforeEach,
  3. [组件] - 如果是路由的参数变化,触发beforeRouteUpdate,
  4. [配置文件]里 - 下一个beforeEnter,
  5. [组件] - 内部声明的beforeRouteEnter,
  6. [全局] - 调用 beforeResolve,
  7. [全局] - router.afterEach,

核心原理,promise的链式调用。