zhaobinglong / myBlog

https://zhaobinglong.github.io/myBlog/
MIT License
7 stars 0 forks source link

vue之路由router #68

Open zhaobinglong opened 4 years ago

zhaobinglong commented 4 years ago

原理

核心更新视图但不重新请求页面。vue-router实现单页面路由跳转。

路由的三种模式

  // vue-router的构造函数
  constructor (options: RouterOptions = {}) {
    let mode = options.mode || 'hash' // 默认为'hash'模式
    this.fallback = mode === 'history' && !supportsPushState // 通过supportsPushState判断浏览器是否支持'history'模式
    if (this.fallback) {
      mode = 'hash'
    }
    if (!inBrowser) {
      mode = 'abstract' // 不在浏览器环境下运行需强制为'abstract'模式
    }
    this.mode = mode

    // 根据mode确定history实际的类并实例化
    switch (mode) {
      case 'history':
        this.history = new HTML5History(this, options.base)
        break
      case 'hash':
        this.history = new HashHistory(this, options.base, this.fallback)
        break
      case 'abstract':
        this.history = new AbstractHistory(this, options.base)
        break
      default:
        if (process.env.NODE_ENV !== 'production') {
          assert(false, `invalid mode: ${mode}`)
        }
    }
  }

分类

路由实例化

const router = new Router({
   routes: [
      { path: '/foo', component: Foo }
  ]
  mode: hash // 模式,默认hash,
  // scrollBehavior定义了页面内的滚动条滚动的位置
  scrollBehavior (to, from, savedPosition) {
     return { x: 0, y: 0 }
  }
})

参看

https://zhuanlan.zhihu.com/p/27588422

zhaobinglong commented 4 years ago

全局钩子之进入前钩子

//全局路由改变前钩子
router.beforeEach((to, from, next) => {
  console.log('即将进入:', to);
  console.log('即将离开:', from);
  next();
})

使用场景

beforeEach() 方法还有个常用的地方:验证用户权限。比如某些页面需要先验证是否登录,如果登录了就可以访问,否则跳转到登录页。这个判断就可以放在 beforeEach() 方法中进行。

//全局路由改变前钩子
router.beforeEach((to, from, next) => {
  //这里简单地使用localStorage来判断是否登陆
  if(window.localStorage.getItem('token')){
    next();
  }else{
    next('/login');
  }
})

全局钩子之离开后钩子

//全局路由改变后钩子
router.afterEach((to, from) => {
  console.log('即将进入:', to);
  console.log('即将离开:', from);
})

使用场景

afterEach() 方法有个常用的地方是自动让页面返回最顶端。比如一个页面较长,滚动到某个位置后跳转。这时另一个页面滚动条默认是上一个页面停留的位置。我们可以在 afterEach() 方法中将滚动条位置进行重置。

zhaobinglong commented 4 years ago

页面内路由钩子

<template>
  <div>
    test1
    <el-button @click="test2">跳转</el-button>
  </div>
</template>

<script>
  export default {
    name: "Test1",
    methods: {
      test2() {
        this.$router.push({name: "test2", query:{type: false}})
      }
    },
    beforeRouteEnter(to, from, next) {
      console.log("****************Test1****Enter**************");
      console.log('to', to);
      console.log('from', from);
      console.log('next', next);
      next(vm => {
        //因为当钩子执行前,组件实例还没被创建
        // vm 就是当前组件的实例相当于上面的 this,所以在 next 方法里你就可以把 vm 当 this 来用了。
        console.log(vm);//当前组件的实例
      });
    },
    beforeRouteUpdate(to, from, next) {
      //在当前路由改变,但是该组件被复用时调用
      //对于一个带有动态参数的路径 /good/:id,在 /good/1 和 /good/2 之间跳转的时候,
      // 由于会渲染同样的good组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
      // 可以访问组件实例 `this`
      console.log("****************Test1*******Update***********");
      console.log(this, 'Update'); //当前组件实例
      console.log('to', to);
      console.log('from', from);
      console.log('next', next);
      next();
    },
    beforeRouteLeave(to, from, next) {
      // 导航离开该组件的对应路由时调用
      // 可以访问组件实例 `this`
      console.log("****************Test1****Leave**************");
      console.log(this, 'Leave'); //当前组件实例
      console.log('to', to);
      console.log('from', from);
      console.log('next', next);
      next();
    }
  }
</script>

<style scoped>

</style>
zhaobinglong commented 4 years ago

页面中的三个路由钩子

(1)beforeRouteEnter (to, from, next) 在该组件的对应路由被 confirm 前调用。(即路由进入前调用) 由于此时实例还没被创建,所以此时不能获取组件实例(this)。

(2)beforeRouteUpdate (to, from, next) 在当前路由改变,但是该组件被复用时调用。比如:我们在一个带有动态参数的路径 /hello/:id 之间跳转(/hello/1 和 /hello/2),虽然渲染的是同样的 Detail 组件,但这个钩子还是会被调用。 该函数内可以访问获取组件实例(this)。

(3)beforeRouteLeave (to, from, next) 当导航离开该组件的对应路由时调用。(即路由离开前调用) 该函数内可以访问获取组件实例(this)。

zhaobinglong commented 4 years ago

路由元信息(meta)配置

const router =  new Router({
  routes: [ //配置路由,使用数组形式
    {
      path: '/',   //链接路径
      name: 'index',  //路由名称
      component: index, //映射的组件
      meta: {
        title: '首页'
      }
    }
  ]
})

在路由元钩子中获取元信息

//全局路由改变前钩子
router.beforeEach((to, from, next) => {
  window.document.title = to.meta.title;
  next();
})
zhaobinglong commented 3 years ago

三大模式之hash模式

即浏览器url中#后面的内容,包含#。hash是URL中的锚点,代表的是网页中的一个位置,单单改变#后的部分,浏览器只会加载相应位置的内容,不会重新加载页面。 也就是说

即#是用来指导浏览器动作的,对服务器端完全无用,HTTP请求中,不包含#。 每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用”后退”按钮,就可以回到上一个位置。 所以说Hash模式通过锚点值的改变,根据不同的值,渲染指定DOM位置的不同数据。

zhaobinglong commented 3 years ago

三大模式之History模式

HTML5 History API提供了一种功能,能让开发人员在不刷新整个页面的情况下修改站点的URL,就是利用 history.pushState API 来完成 URL 跳转而无须重新加载页面; 由于hash模式会在url中自带#,如果不想要很丑的 hash,我们可以用路由的 history 模式,只需要在配置路由规则时,加入"mode: 'history'",这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。

//main.js文件中
const router = new VueRouter({
  mode: 'history',
  routes: [...]
})

注意

要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面

export const routes = [ 
  {path: "/", name: "homeLink", component:Home}
  {path: "/register", name: "registerLink", component: Register},
  {path: "/login", name: "loginLink", component: Login},
  {path: "*", redirect: "/"}]
zhaobinglong commented 3 years ago

页面更新步骤

1 $router.push() //调用方法

2 HashHistory.push() //根据hash模式调用,设置hash并添加到浏览器历史记录(添加到栈顶)(window.location.hash= XXX)

3 History.transitionTo() //监测更新,更新则调用History.updateRoute()

4 History.updateRoute() //更新路由

5 {app._route= route} //替换当前app路由

6 vm.render() //更新视图

zhaobinglong commented 3 years ago

动态路由匹配

我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染

// 著名问题:url中的参数变化了,但是页面内容没有变化,这是因为组件被复用,就没有再次走生命周期
const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User }     // 动态路径参数 以冒号开头
  ] 
})

解决参数变化但是页面不刷新

const User = {
  template: '...',
  // 监听路由中参数的变化
  watch: {
    $route(to, from) {
      // 对路由变化作出响应...
    }
  }
}

// 利用导航守卫beforeRouteUpdate
const User = {
  template: '...',
  beforeRouteUpdate (to, from, next) {
    // react to route changes...
    // don't forget to call next()
  }
}