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() 返回的是 Promise
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
一个 store.dispatch 在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。