vue-router

114 阅读2分钟

路由背景

  1. 什么是 router 以及 router 发展的历史
  • 路由是伴随着 SPA 出现的,在此之前,页面的跳转是后端实现的

现在的模式:客户端向后台请求数据,后台组织好 json 发回给前端

image.png

以前的模式:前端表单给后端一个formdata,后端有个controller验证信息,再从controller中取出一个模版,讲数据注入,然后把模版传回前端;

image.png

  • 传统的⻚面的跳转,是通过前端向后台发送请求;
  • 后台通过模板引擎的渲染,将一个新的html界面
  • 比如⻚面跳转时:
    • from表单的提交;
    • a标签的默认属性;
    • js调用location.href,给其赋值;
    • H5: history 的 go / forward / back -- // history.push / replace ?
  • 在 SPA 的出现之后,前端可以自由的控制组建的渲染,来模拟页面的跳转。
    • hash 路由和 history 路由 总结:
  • 传统的路由,是根据 url 访问相关的 controller 进行数据资源和模版的拼接,返回前端;
  • 前端路由使用过 JS 根据 url 返回对应的组建加载 * 所以,前端路由包含两个部分:
    • url 的处理
    • 组建加载

简单的 hash 路由 原理:

  • 路由发生改变,改变可以被监听到,改变又可以触发事件

模拟一个 hash 路由:

class BaseRouter {
  constructor() {
    // 初始化一个依赖
    this.routes = {};
    this.refresh = this.refresh.bind(this);

    // 解决刷新后消失
    window.addEventListener('load', this.refresh);
    // 考虑到刷新
    window.addEventListener('hashchange', this.refresh);
  }

  route(path, callback) {
    this.routes[path] = callback || function() {}
  }

  refresh() {
    const path = `/${window.location.hash.slice(1) || ''}`;
    this.routes[path]();
  }
}

const Route = new BaseRouter();

Route.route('/about', () => changeText('关于我们'));
Route.route('/user', () => changeText('用户详情'));
Route.route('/', () => changeText('首页'));

function changeText(arg) {
  document.getElementById('context').innerHTML = arg;
}
<div id="container">
  <button onclick="window.location.hash='#'">首页</button>
  <button onclick="window.location.hash='#about'">关于我们</button>
  <button onclick="window.location.hash='#user'">用户详情</button>

  <div id="context"></div>
</div>

模拟一个 history 路由: t

<div id="container">
  <a href="./">首页</a>
  <a href="./about">关于我们</a>
  <a href="./user">用户详情</a>
</div>
<div id="context"></div>
class BaseRouter {
  // 初始化依赖
  constructor () {
    this.routes = {};
    // 刷新问题
    this.init();
    // 解决无法调用前进后退
    this._bindPopState();
  }

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

  route(path, callback) {
    this.routes[path] = callback || function() {};
  }

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

  // 内部函数 不暴露
  _bindPopState() {
    window.addEventListener('popstate', e => {
      const path = e.state && e.state.path;
      this.routes[path] && this.routes[path]();
    })
  }
}

const Route = new BaseRouter();

Route.route('./about', () => changeText('关于我们'));
Route.route('./user', () => changeText('用户详情'));
Route.route('./', () => changeText('首页'));

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

container.addEventListener('click', e => {
  if (e.target.tagName === 'A') {
    e.preventDefault();
    Route.go(e.target.getAttribute('href'));
  }
})
  1. 动态路由 静态路由
import About from '../views/About.vue'

{
  path: '/about',
  name: 'About',
  component: About
}

动态路由: 会重新请求一个about.js

{
  path: '/about',
  name: 'About',
  component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}

其实,动态路由 包括 React.lazyimport() 就是一种对代码进行拆分的技术,一般叫做 code splitting 在需要的时候,才进行加载;

动态路由拆包越多,请求越多,拆包越少,bundle体积越大,要做个平衡

动态路由/异步组件,就是需要让首屏加载是 bundle 大小最小

  1. router 的配置: 入口处 router-link 跳转链接;router-view 加载的内容; router.js 中的home,about 对应加载到router-view 中

  2. 路由守卫 路由跳转的时候写一些函数钩子 分为:组件中的路由守卫,全局路由守卫

全局路由守卫

// router/index.js
router.beforeEach((to, from, next) => {
  console.log("全局-beforeEach", to);
  next();
});

router.beforeResolve((to, from, next) => {
  console.log("全局-beforeResolve", to);
  next();
});

router.afterEach((to, from, next) => {
  console.log("全局-afterEach", to);
  next();
});

组件中的路由守卫

// Home.vue
beforeRouterEnter(from, to, next) {
  console.log('组件内-beforeRouterEnter', to);
  next();
},
beforerouterUpdate(from, to, next) {
  console.log('组件内-beforerouterUpdate', to);
  next();
},
beforeRouterLeave(from, to, next) {
  console.log('组件内-beforeRouterLeave', to);
  next();
},

4.1 路由守卫触发流程

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

题目总结

  1. hash 路由和 history 路由的区别?
  • hash 路由一般会携带一个#号,不够美观;history 路由不存在
  • 默认 hash 路由是不会向浏览器端发出请求的,主要一般适用于锚点(用于定位页面中某部分内容);history 中 go/back/forward 以及浏览器中的前进和后退按钮,一般都会向服务器端发出请求 -- history 的所有url内容,服务器端都可以获取到
  • 基于此,hash 路由不支持 SSR 的,但 history 路由是可以的
  • history 路由在部署的时候,如 nginx,只需要渲染首页,让首页根据路径重新跳转
  • hash 路由的监听,一般用onhashChange 事件监听; history 路由监听,一般是 onPopState 监听
  • nginx 如何部署:
# 单个服务器的部署
location / {
  try_files uri $uri /xxx/main/index.html
}
// 面试了解即可

# 存在代理的情况
location / {
  rewrite ^ /file/index.html break; # 这里代表的是 xxx.cdn 的资源路径
  proxy_pass https://www.xxx.cdn.com
}
  1. history history 是一个 BOM api,里面有 go/forward/back 三个API,以及pushState/replaceState;
  • pushState/replaceState 都不会触发popState 事件,(不一定会重新渲染)
  • popState 什么时候触发?
    • 点击浏览器前进,后退按钮
    • back/forward/go
  1. 实现一个hash/history