[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)

這個狀態自管理應用包含以下幾個部分:
* **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 基礎
- React + ts
- 樣式設計
- 組件測試
- 圖標解決方案
- Storybook 的使用
- Input 組件
- 在線 mock server
- 打包與發布
- Algorithm
- 排序算法及常見問題
- 劍指 offer
- 動態規劃
- DataStruct
- 概述
- 樹
- 鏈表
- Network
- Performance
- Webpack
- PWA
- Browser
- Safety
- 微信小程序
- mpvue 課程實戰記錄
- 服務器
- 操作系統基礎知識
- Linux
- Nginx
- redis
- node.js
- 基礎及原生模塊
- express框架
- node.js操作數據庫
- 《深入淺出 node.js》筆記
- 前半部分
- 后半部分
- 數據庫
- SQL
- 面試題收集
- 智力題
- 面試題精選1
- 面試題精選2
- 問答篇
- 2025面試題收集
- Other
- markdown 書寫
- Git
- LaTex 常用命令
- Bugs