[TOC]
# vue-router
## \<router-link> 与 \<router-view>
```html
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<h1>Hello App!</h1>
<p>
<!-- 使用 router-link 组件来导航. -->
<!-- 通过传入 `to` 属性指定链接. -->
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
```
## 定义路由及路由匹配
项目中一般会单独将路由抽离为一个 store.js 文件,如下:
```js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
redirect: '/store'
},
{
path: '/ebook',
component: () => import('./views/ebook/index.vue'), // 路由懒加载,这里用的是ES6的语法 import()函数是动态加载 import 是静态加载
children: [
{
path: ':fileName', // 动态路由, 可以传递路径参数
component: () => import('./components/ebook/EbookReader.vue')
}
]
},
{
path: '/store',
component: () => import('./views/store/index.vue'),
redirect: '/store/shelf', // #/store -> #/store/home
children: [
{
path: 'home', // children 使用相对路径 可以通过 store/home 来找到
component: () => import('./views/store/StoreHome.vue')
},
{
path: 'list',
component: () => import('./views/store/StoreList.vue')
},
{
path: 'detail', // /store/detail 加斜杠则只能匹配/detail
component: () => import('./views/store/StoreDetail.vue')
},
]
}
]
})
```
然后还需要再 main.js 中引入:
```js
import router from './router'
new Vue({
router,
store,
...
render: h => h(App)
}).$mount('#app')
```
上面有涉及 <span style="font-family:楷体;font-weight:700;">动态路径参数</span> 的概念:你可以在一个路由中设置多段“路径参数”,对应的值都会设置到`$route.params`中。例如
| 模式 | 匹配路径 | $route.params |
| --- | --- | --- |
| /user/:username | /user/evan | `{ username: 'evan' }` |
| /user/:username/post/:post\_id | /user/evan/post/123 | `{ username: 'evan', post_id: '123' }` |
同时也涉及到了 <span style="font-family:楷体;font-weight:700;">嵌套路由</span> :
```js
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
}
]
}
]
})
```
>[danger] 注意:以 / 开头的嵌套路径会被当作根路径
## 组件访问路由对象
可以在任何组件内通过`this.$router`访问路由器,也可以通过`this.$route`访问当前路由。
`$route`是 <span style="font-family:楷体;font-weight:700;">路由信息对象</span>,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数。而 `$router` 是 <span style="font-family:楷体;font-weight:700;">路由实例对象</span>,包括了路由的跳转方法,钩子函数等。
## 路由跳转及获取参数
| 声明式 | 编程式 |
| --- | --- |
| `<router-link :to="...">` | `router.push(...)` |
```js
// 字符串
router.push('home') // 一般是在组件内部使用,即 this.$router.push()
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
```
```js
// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1) // 一般是在组件内部使用,即 this.$router.go()
// 后退一步记录,等同于 history.back()
router.go(-1)
// 前进 3 步记录
router.go(3)
```
前面有提到过:`route`是路由信息对象,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数,那么显然我们获取当前路由的一些信息就需要通过`route`对象,下面主要看看`params、query`这两个参数。
```js
// query 传参,使用 name 跳转 (name 是你为路由取的别名)
this.$router.push({
name: 'detailPage',
query: {
queryId: '12345',
queryName: 'query'
}
})
// query 传参,使用 path 跳转
this.$router.push({
path: '/detail',
query: {
queryId: '12345',
queryName: 'query'
}
})
// 跳转到的路由组件接收参数
const id = this.$route.query.queryId
const name = this.$route.query.queryName
```
用 query 传参你的 URL 会类似这样`localhost:8080/#/detail?queryId=12345&queryName=query`
而 params 实际上是与动态路径参数相对应的,这个概念我们在上面也提到过
| 模式 | 匹配路径 | $route.params |
| --- | --- | --- |
| /user/:username | /user/evan | `{ username: 'evan' }` |
| /user/:username/post/:post\_id | /user/evan/post/123 | `{ username: 'evan', post_id: '123' }` |
来看看官方给出的例子:如果提供了`path`,`params`会被忽略,你需要提供路由的`name`或手写完整的带有参数的`path`:
```js
const userId = '123'
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user
```
看到这就很明显了,如果你定义路由时没有采用动态路径参数的形式,那么一般都是采用 query 来传递信息,否则,可以使用 params 来传递信息。
## hash 模式与 history 模式
<span style="font-size:20px; color: #42b983;">hash 模式</span>
在浏览器中符号 “#”,# 以及 # 后面的字符称之为 hash,用 window.location.hash 读取;
特点:hash 虽然在 URL 中,但不被包括在 HTTP 请求中;用来指导浏览器动作,对服务端安全无用,hash 不会重加载页面。
hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 `http://www.xxx.com`,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。
*****
<span style="font-size:20px; color: #42b983;">history 模式</span>
history 采用 HTML5 的新特性;且提供了两个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及 popState 事件的监听到状态变更。
history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 `http://www.xxx.com/items/id`。后端如果缺少对 `/items/id` 的路由处理,将返回 404 错误。
使用该模式一般需要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。
hash 模式和 history 模式并没有改变路由跳转的语法,只是 history 模式的路由需要后台的支持,关键是 URL:hash 模式仅 hash 符号('#’)之前的内容会被包含在请求中。
*****
`vue-router`默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。
如果想使用 history 模式,可以在 cli 构建时更换模式即可,或者:
```js
const router = new VueRouter({
mode: 'history',
routes: [...]
})
```
## 导航守卫
还没用过....[https://router.vuejs.org/zh/guide/advanced/navigation-guards.html#%E7%BB%84%E4%BB%B6%E5%86%85%E7%9A%84%E5%AE%88%E5%8D%AB](https://router.vuejs.org/zh/guide/advanced/navigation-guards.html#%E7%BB%84%E4%BB%B6%E5%86%85%E7%9A%84%E5%AE%88%E5%8D%AB)
# vuex
## 基础概念
每一个 Vuex 应用的核心就是 **store(仓库)**。store 基本上就是一个容器,它包含着你的应用中大部分的 **状态 (state)**。
Vuex 和单纯的全局对象有以下两点不同:
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到更新。
- 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit)
![](https://box.kancloud.cn/5145ed5562bb79d0e9b02155a5c1f2ac_787x569.png)
这个状态自管理应用包含以下几个部分:
* **state**,驱动应用的数据源;
* **view**,以声明方式将 **state** 映射到视图;
* **actions**,响应在 **view** 上的用户输入导致的状态变化。
一般将 store 放在一个单独的 store.js 文件或 store 文件夹下,创建一个最简单的 store:
```js
// store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
privilege: 0
},
/*
每个 mutation 都有一个字符串的事件类型 (type) 和 一个回调函数 (handler);
这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数
*/
mutations: {
'SET_PRIVILEGE': (state, newPrivilege) => {
state.privilege = newPrivilege
}
},
/*
Action 提交的是 mutation,而不是直接变更状态。
Action 可以包含任意异步操作。
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,
或者通过 context.state 和 context.getters 来获取 state 和 getters。
当我们在之后介绍到 Modules 时,你就知道 context 对象为什么不是 store 实例本身了。
*/
actions: {
setPrivilege({ commit }, newPrivilege) {
return commit('SET_PRIVILEGE', newPrivilege)
}
}
})
```
然后我们还需要在 main.js 中引入:
```js
import store from './store'
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
```
> 为什么 vuex 不像 redux 需要引入中间件而可以直接处理异步操作呢?
## 组件中获取 vuex 的 state
跟 redux 一样,vuex 使用单一状态树,即每个应用仅应该包含一个 store 实例。
方法 1:子组件在 **计算属性** 中返回某个状态(基于 Vuex 的状态存储是响应式的)
如下面的代码,每当`store.state.count`变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM。
```js
// 创建一个 Counter 组件
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return store.state.count
}
}
}
```
方法 2:通过 store 选项将状态从根组件"注入"到每个子组件(使用 vue-cli3 构建项目时自动使用该方式)
通过在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 `this.$store `访问到
```js
const app = new Vue({
el: '#app',
// 把 store 对象提供给 "store" 选项,这可以把 store 的实例注入所有的子组件
store,
components: { Counter },
template: `
<div class="app">
<counter></counter>
</div>
`
})
```
```js
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count
}
}
}
```
方法 3:使用 mapState 辅助函数(感觉可以被 mapGetter 替代?)
方法 4:使用 mapGetter(并不是单纯的映射关系,可以做一些处理)
Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
Getter 接受 state 作为其第一个参数:
```js
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
```
`mapGetters`辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:
```js
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
```
如果你想将一个 getter 属性另取一个名字,使用对象形式:
```js
mapGetters({
// 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})
```
## Mutation 与 Action
### 在组件中分发 action
在组件中使用`this.$store.dispatch('xxx')`分发 action,或者使用`mapActions`辅助函数将组件的 methods 映射为`store.dispatch`调用(需要先在根节点注入`store`):
```js
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
`store.dispatch`可以处理被触发的 action 的处理函数返回的 Promise,并且`store.dispatch`仍旧返回 Promise
```js
actions: {
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
```
使用 async / await:
```js
// 假设 getData() 和 getOtherData() 返回的是 Promise
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
```
## 划分 Module
Vuex 允许我们将 store 分割成**模块(module)**。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
```js
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
```
对于模块内部的 mutation 和 getter,接收的第一个参数是**模块的局部状态对象**。
```js
const moduleA = {
state: { count: 0 },
mutations: {
increment (state) {
// 这里的 `state` 对象是模块的局部状态
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
```
项目结构:
```shell
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块
```
完整的 vuex 项目结构示例 [点击这里](https://github.com/ChenMingK/epub-Proj/tree/master/src/store)
- 序言 & 更新日志
- H5
- Canvas
- 序言
- Part1-直线、矩形、多边形
- Part2-曲线图形
- Part3-线条操作
- Part4-文本操作
- Part5-图像操作
- Part6-变形操作
- Part7-像素操作
- Part8-渐变与阴影
- Part9-路径与状态
- Part10-物理动画
- Part11-边界检测
- Part12-碰撞检测
- Part13-用户交互
- Part14-高级动画
- CSS
- SCSS
- codePen
- 速查表
- 面试题
- 《CSS Secrets》
- SVG
- 移动端适配
- 滤镜(filter)的使用
- JS
- 基础概念
- 作用域、作用域链、闭包
- this
- 原型与继承
- 数组、字符串、Map、Set方法整理
- 垃圾回收机制
- DOM
- BOM
- 事件循环
- 严格模式
- 正则表达式
- ES6部分
- 设计模式
- AJAX
- 模块化
- 读冴羽博客笔记
- 第一部分总结-深入JS系列
- 第二部分总结-专题系列
- 第三部分总结-ES6系列
- 网络请求中的数据类型
- 事件
- 表单
- 函数式编程
- Tips
- JS-Coding
- Framework
- Vue
- 书写规范
- 基础
- vue-router & vuex
- 深入浅出 Vue
- 响应式原理及其他
- new Vue 发生了什么
- 组件化
- 编译流程
- Vue Router
- Vuex
- 前端路由的简单实现
- React
- 基础
- 书写规范
- Redux & react-router
- immutable.js
- CSS 管理
- React 16新特性-Fiber 与 Hook
- 《深入浅出React和Redux》笔记
- 前半部分
- 后半部分
- react-transition-group
- Vue 与 React 的对比
- 工程化与架构
- Hybird
- React Native
- 新手上路
- 内置组件
- 常用插件
- 问题记录
- Echarts
- 基础
- Electron
- 序言
- 配置 Electron 开发环境 & 基础概念
- React + TypeScript 仿 Antd
- TypeScript 基础
- 样式设计
- 组件测试
- 图标解决方案
- Algorithm
- 排序算法及常见问题
- 剑指 offer
- 动态规划
- DataStruct
- 概述
- 树
- 链表
- Network
- Performance
- Webpack
- PWA
- Browser
- Safety
- 微信小程序
- mpvue 课程实战记录
- 服务器
- 操作系统基础知识
- Linux
- Nginx
- redis
- node.js
- 基础及原生模块
- express框架
- node.js操作数据库
- 《深入浅出 node.js》笔记
- 前半部分
- 后半部分
- 数据库
- SQL
- 面试题收集
- 智力题
- 面试题精选1
- 面试题精选2
- 问答篇
- Other
- markdown 书写
- Git
- LaTex 常用命令