前端路由: hash & history 模式

1,343 阅读2分钟

一、 history 模式

history原理: 利用H5的 history中新增的两个API pushState() 和 replaceState() 和一个事件onpopstate

pushState是往history对象里添加一个新的历史记录,即压栈。 replaceState 是替换history对象中的当前历史。 这两个api 接受三个参数:

history.pushState(
    {
        key: 'value'    // 状态对象
    },
    'title',            // 标题
    'url'               // url
)

当点击浏览器后退按钮或js调用history.back都会触发onpopstate事件

缺陷:

直接访问二级页面或者在页面内刷新,会出现404错误

解决方法:

后端ngnix中配置重定向到首页路由上

二、hash模式

原理是监听onhashchange()事件。仅 hash 符号之前的内容会被包含在请求中,如 www.abc.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。

三、[vue-router][1]

vue-router默认hash模式

3.1 访问路由器

this.$router  // 访问路由器
this.$route   // 访问当前路由

3.2 动态路由匹配

const router = new VueRouter({
  routes: [
    // 动态路径参数 以冒号开头
    { path: '/user/:id', component: User }
  ]
})

const User = {
  template: '<div>User {{ $route.params.id }}</div>'
}

3.3 嵌套路由

要在嵌套的出口中渲染组件,需要在 VueRouter 的参数中使用 children 配置:

const User = {
  template: `
    <div class="user">
      <h2>User {{ $route.params.id }}</h2>
      <router-view></router-view>
    </div>
  `
}
const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User,
      children: [
        {
          // 当 /user/:id/profile 匹配成功,
          // UserProfile 会被渲染在 User 的 <router-view> 中
          path: 'profile',
          component: UserProfile
        },
        {
          // 当 /user/:id/posts 匹配成功
          // UserPosts 会被渲染在 User 的 <router-view> 中
          path: 'posts',
          component: UserPosts
        }
      ]
    }
  ]
})

3.4 编程式导航

router.push():向 history 栈添加一个新的记录 router.replace():替换掉当前的 history 记录。 router.go(n): 在 history 记录中向前或者后退多少步 效仿window.history API,且在各类路由模式(history,hash)下表现一致

// 字符串
router.push('home')

// 对象
router.push({ path: 'home' })

// 命名的路由(如果提供了 path,params 会被忽略)
router.push({ name: 'user', params: { userId: 123 }})

// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})

3.5 导航守卫

全局守卫

const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
  // to: Route: 即将要进入的目标 路由对象
  // from: Route: 当前导航正要离开的路由
  // next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数
})

全局后置钩子

router.afterEach((to, from) => {
  // ...
})

3.6 路由元信息

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      children: [
        {
          path: 'bar',
          component: Bar,
          // a meta field
          meta: { requiresAuth: true }
        }
      ]
    }
  ]
})

// 访问meta对象
// 一个路由匹配到的所有路由记录会暴露为 $route 对象 (还有在导航守卫中的路由对象) 的 $route.matched 数组。因此,我们需要遍历 $route.matched 来检查路由记录中的 meta 字段。
router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    // this route requires auth, check if logged in
    // if not, redirect to login page.
    if (!auth.loggedIn()) {
      next({
        path: '/login',
        query: { redirect: to.fullPath }
      })
    } else {
      next()
    }
  } else {
    next() // 确保一定要调用 next()
  }
})

3.7 路由懒加载

使用动态 import语法来定义代码分块点 (split point): 如果您使用的是 Babel,你将需要添加 syntax-dynamic-import 插件,才能使 Babel 可以正确地解析语法。

const router = new VueRouter({
  routes: [
    { path: '/foo', component: () => import('./Foo.vue') }
  ]
})