Vue Router的一些总结

483 阅读2分钟

Vue Router

路由元信息

如果你希望能将任意信息附加到路由上,如过渡名称、谁可以访问路由等。这些事情可以通过接收属性对象的meta属性来实现,并且它可以在路由地址和导航守卫上都被访问到。定义路由的时候你可以这样配置meta字段:

const routes = [
    {
        path: '/posts',
        component: PostsLayout,
        children: [
            {
                path: 'new',
                component: PostNew,
                //只有经过身份验证的用户才能创建帖子
                meta: {
                    requiresAuth: true
                }
            },
            {
                path: ':id',
                component: PostsDetail
                //任何人都可以阅读文章
                meta: {
                    requiresAuth: false
                }
            }
        ]
    }
]

那么如何访问这个meta字段呢?

首先,我们称呼routes配置中的每个路由为路由记录。路由记录可以是嵌套的,因此,当一个路由匹配成功后,它可能匹配多个路由记录。

例如,根据上面的路由配置,/posts/new这个URL将会匹配父路由记录以及子路由记录(path: 'new')。

一个路由匹配到的所有路有记录会暴露$route对象(还有在导航守卫中的路由对象)的$route.matched数组。我们需要遍历这个数组来检查路由记录中的meta字段,但是Vue Router还为你提供了一$route.meta方法,它是一个非递归合并所有meta字段的(从父字段到子字段)的方法。这意味这你可以简单地写

router.beforeEach((to, from) => {
    if (to.meta.requiresAuth && !auth.isLoggedIn()) {
        //此路由需要授权,请检查是否已登录
        //如果没有,则重定向到登录页面
        return {
            path: '/login',
            //保存我们的位置,以便以后再来
            query: {
                redirect: to.fullPath
            },
        }
    }
})

TypeScript

可以通过扩展RouteMeta接口来输入meta字段:

import 'vue-router'
declear module 'vue-router' {
    interface RouteMeta {
        isAdmin?: boolean
        requireresAuth: boolean
    }
}

RouterOptions

history

用于路由实现历史记录。大多数应用程序都应该使用createWebHistory,但它要求正确配置服务器。你还可以使用createWebHashhistory 的基于hash的历史记录,它不需要在服务器上进行任何配置,但是搜索引擎根本不会处理它,它在SEO上表现很差。

RouterRecordRaw

当用户通过routes option或者router.addRoute()来添加路由时,可以得到路由记录,有三种不同的路有记录:

  • 单一视图记录:有一个component配置
  • 多视图记录(命名记录):有一个components配置
  • 重定向记录:没有component或components配置,因为重定向记录永远不会到达。

前端路由与后端路由

什么是路由

路由是URL到函数的映射

后端路由

在早期web开发时代,前端功能远不如现在强大,一直是后端路由占据主导地位。用户能通过URL访问到的页面,大多数都是后端路由匹配后再返回给浏览器的,浏览器在地址栏中切换不同的URL时,每次都向后台服务器发送请求,服务器响应请求后,在后台拼接html文件并返回给前端 ,并且每次切换页面时,浏览器都会刷新页面。

在后端,路由映射表中就是不用的URL地址与不同的html+css+后端数据之间的映射

前端路由

前端路由的好处

不会出现白屏

前端路由主要有两种模式

  • hash:带有hash的前端路由,优点是兼容性高。缺点是URL带着#不好看
  • history:不带hash的前端路由,优点是URL不带#号,缺点是既需要浏览器支持,也需要后端服务器支持。

整个页面就只有一整套HTML+CSS+JS,当我们请求URL时,客户端会从这一套HTML+CSS+JS中找到对应的HTML+CSS+JS,并将他们解析渲染在页面上。

Hash模式

早期的前端路由实现是基于location.hash来实现的,location.hash就是路由#后面的内容,其原理就是通过hashchange监听#后面的内容的变化来进行页面更新。hash模式是利用浏览器不会对#后面的路径对服务端发起请求。

  • 改变hash值,浏览器不会重新加载页面
  • 当刷新页面时,hash不会传给服务器

优点:

  • 可以兼容低版本浏览器
  • 只有#之前的内容才会作为URL发送给服务器,就算服务端没有对路由进行全覆盖也不会返回404
  • hash改变都会在浏览器访问历史记录中增加一个记录,所以可以通过浏览器进行前进后退,如果想在hash模式下不保存记录,可以使用replace

history模式

history是基于HTML5新增的pushState和replaceState两个API以及浏览器的popState事件监听历史栈的改变,只要历史栈有信息发生变化,就会触发该事件。这种模式同样不会向后端发起请求的。

优点:

  • 该模式的路由不带#,看起来美观
  • pushState设置的URL可以是任意的与当前URL同源的URL。而hash只能改变#后面的内容

缺点:

IE9及其以下版本浏览器不支持,IE10开始支持

vue-router会检测浏览器版本,当无法启用history模式时会自动降级为hash模式

Action

Action的两种写法

Action类似于mutation,不同在于:

  • Action提交的是mutation,而不是直接变更状态
  • Action可以包含任意异步操作

注册一个简单的action

const store = createStore({
    state: {
        count: 0
    },
    mutations: {
        increment(state) {
            state.count++
        }
    },
    actions: {
        increment (context) {
            context.commit('increment')
        }
    }
})

Action函数接收一个与store实例具有相同方法和属性的context对象,因此你可以调用context.commit提交一个mutation,或者通过context.state和context.getters来获取state和getters。

实践中,我们会经常用ES6的参数解构来简化代码(特别是我们需要调用commit很多次的时候):

action: {
    increment({ commit }) {
        commit('increment')
    }
}

分发Action

Action通过store.dispatch方式触发:

store.disptach('increment')

乍一眼看上去感觉多此一举,我们直接分发 mutation 岂不更方便?实际上并非如此,还记得 mutation 必须同步执行这个限制么?Action 就不受约束!我们可以在 action 内部执行异步操作:

actions: {
  incrementAsync ({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  }
}

Actions 支持同样的载荷方式和对象方式进行分发:

// 以载荷形式分发
store.dispatch('incrementAsync', {
  amount: 10
})
​
// 以对象形式分发
store.dispatch({
  type: 'incrementAsync',
  amount: 10
})

一个更加实际的购物车示例,涉及到调用异步 API分发多重 mutation

actions: {
  checkout ({ commit, state }, products) {
    // 把当前购物车的物品备份起来
    const savedCartItems = [...state.cart.added]
    // 发出结账请求,然后乐观地清空购物车
    commit(types.CHECKOUT_REQUEST)
    // 购物 API 接受一个成功回调和一个失败回调
    shop.buyProducts(
      products,
      // 成功操作
      () => commit(types.CHECKOUT_SUCCESS),
      // 失败操作
      () => commit(types.CHECKOUT_FAILURE, savedCartItems)
    )
  }
}

注意我们正在进行一系列的异步操作,并且通过提交 mutation 来记录 action 产生的副作用(即状态变更)。

在组件中分发Action

你在组件中使用 this.$store.dispatch('xxx') 分发 action,或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store):

import { mapActions } from 'vuex'export default {
  // ...
  methods: {
    ...mapActions([
      'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
​
      // `mapActions` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
    })
  }
}

组合Action

Action 通常是异步的,那么如何知道 action 什么时候结束呢?更重要的是,我们如何才能组合多个 action,以处理更加复杂的异步流程?

首先,你需要明白 store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise:

actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  }
}

现在你可以:

store.dispatch('actionA').then(() => {
  // ...
})

在另外一个 action 中也可以:

actions: {
  // ...
  actionB ({ dispatch, commit }) {
    return dispatch('actionA').then(() => {
      commit('someOtherMutation')
    })
  }
}

最后,如果我们利用 async / await,我们可以如下组合 action:

// 假设 getData() 和 getOtherData() 返回的是 Promiseactions: {
  async actionA ({ commit }) {
    commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // 等待 actionA 完成
    commit('gotOtherData', await getOtherData())
  }
}

一个 store.dispatch 在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。