vueRouter & vueX
看到這里的朋友如果沒有看過前幾期,可以通過文章的鏈接跳轉到第一期,從第一期的 vue2 語法開始學習,如果是復習的朋友,也可以看本期只學習 vueRouter & VueX
項目初始化
經過上期,我們學習了 vue cli 和組件通信以及插槽,這期來學習 vueRouter & vueX
首先這次也是使用 vue cli 創建一個項目,但是這次我們需要自己選擇依賴,先選擇一個文件夾,放置項目
使用cmd
vue create vue2-vuerouter
這里選擇 Manually select features
把 router 和 vuex 選擇上
這里通過上下鍵移動, 空格鍵選擇
選擇完成按回車創建項目
選擇 vue2
這里選擇 y
使用 history 的方式,下面會講解 history 和 hash 方式的區別
這里不選擇 eslink ,就選擇第一個
選擇 link on sava
選擇 in package.json
這里不要選擇保存歷史,因為現在只是學習階段
等待安裝依賴項目
這里安裝完成后,可以使用命令啟動項目。
cd vue2-vuerouter
npm run serve
項目啟動成功在 8080 端口,打開瀏覽器訪問:
然后這個 Home 和 About 就可以讓我們在不切換頁面的情況下作頁面內容的跳轉,其實這是一個但也應用
hash 和 history的區別:
其實路由是有兩種表現樣式的:
第一種:http://localhost:8080/about (history)
優點: 看起來好看,不容易讓用戶察覺出這是一個單頁面應用
缺點: 可能不兼容低版本瀏覽器
第二種:http://localhsot:/#/about (hash)
優點: 兼容低版本瀏覽器
缺點: 看起來不好看,用戶知道是單頁面應用
分析目錄結構:
打開項目使用開發者工具:
vue2-vuerouter/
├── .idea/ # IDE 配置文件 (WebStorm/IntelliJ)
├── node_modules/ # 第三方依賴庫 (標記為 libraryroot)
├── public/ # 靜態公共資源
├── src/ # 源代碼目錄
│ ├── assets/ # 靜態資源(圖片/樣式等)
│ ├── components/ # 公共組件
│ ├── router/ # 路由配置
│ ├── store/ # Vuex 狀態管理
│ ├── views/ # 頁面級組件
│ ├── App.vue # 根組件
│ └── main.js # 應用入口文件
├── .gitignore # Git忽略規則
├── babel.config.js # Babel配置
├── jsconfig.json # JS路徑配置
├── package.json # 項目配置和依賴
├── package-lock.json # 依賴版本鎖定
├── README.md # 項目說明文檔
└── vue.config.js # Vue自定義配置
這里發現多了 router 還有 store 還有 view 這幾個包,這是我們引入的 vueRouter 還有 Vuex
vueRouter
仔細觀察,會發現,這里主頁的兩個頁面分別是 view 包中的兩個組件
也就是說,我們只要通過 vueRouter 的配置,就可以添加頁面
這里我們新建一個 testView 頁面試試
<script setup></script><template>
<div>這是test測試頁面
</div>
</template><style scoped></style>
找到 router/index.js 添加配置
這里我們先可以分析一下源代碼
首先導入 Vue 實例, 導入 VueRouter 實例,導入 HomeView 組件,并且 Vue.use 使用開啟 VueRouter
緊接著是一個數組,這個數組種放置我們的路由配置
最后 new 了一個 VueRouter , 使用 history 模式, base 是我們服務器的默認地址, routes 是上面定義的頁面配置文件
最后導出 router 實例
所以 Vue cli 都幫我們做好配置了, 我們只需要添加頁面
這里再 routes 數組中添加我們的頁面
這里我們添加了,但是注意一個點,我們還需要一個按鈕,來跳轉到這個界面,這是需要我們自己定義的
在 app.vue 中添加 router-link to=“/test”
這里需要講解一下這兩個標簽的作用了:
router-link to
這個標簽是用來進行路由跳轉的時候,需要我們自己有一個跳轉的 link 位置
router-view
這個標簽也就是我們 Home 或者 About 或者 Test ,他會根據我們點的標簽的路由, 渲染我們在 routes 數組中配置的路由地址
動態路由:
這里為了方便演示,我們再次添加一個路由:
- 新建 ArticleView 組件
- 配置文件 routes 添加
- 添加一個router-link to
代碼:
App.vue
<template><div id="app"><nav><router-link to="/">Home</router-link> |<router-link to="/about">About</router-link>|<router-link to="/test">Test</router-link>|<router-link to="/article">Article</router-link></nav><router-view/></div>
</template><style>
#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;
}nav {padding: 30px;
}nav a {font-weight: bold;color: #2c3e50;
}nav a.router-link-exact-active {color: #42b983;
}
</style>
<script setup lang="ts">
</script>
/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'
import TestView from "../views/TestView.vue";
import ArticleView from "@/views/ArticleView.vue";Vue.use(VueRouter)const routes = [{path: '/',name: 'home',component: HomeView},{path: '/test',name: 'test',component: TestView,},{path: '/article',name: 'article',component: ArticleView,},{path: '/about',name: 'about',// route level code-splitting// this generates a separate chunk (about.[hash].js) for this route// which is lazy-loaded when the route is visited.component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')}
]const router = new VueRouter({mode: 'history',base: process.env.BASE_URL,routes
})export default router
view/Article.vue
<script setup></script><template>
<div>這是一個文章組件
</div>
</template><style scoped></style>
先看效果:
這里如何修改為動態路由呢?
- 在 routes 中的 path 添加 /: id
并且 props :true,這部很重要,可以使用 props 接收
/ : 加上你的參數
這就是一個動態路由
- router-link to 添加一個id
由于我們現在是靜態的只能這樣演示
我們可以在文章組件中獲取到當前文章 id ,這樣可以展示文章詳情
嵌套路由:
對于每一個路由,都可以有子路由
這里配置一個 Article 文章組件的子路由
- 配置 routes 為路由配置 children 子路由屬性
{path: '/article/:id',name: 'article',component: ArticleView,props: true,children:[{path:'info1',name:'info1',component: info1View,},{path:'info2',name:'info2',component: info2View,},]},
- 添加 link to
<script>
export default {name: 'ArticleView',props:{id:{type:Number,required:true}}
}
</script><template>
<div>這是一個文章組件<div>文章id為:{{id}}</div><div><router-link :to="`/article/${id}/info1`">info1</router-link> |<router-link :to="`/article/${id}/info2`">info2</router-link></div><router-view></router-view>
</div>
</template><style scoped></style>
這里的 link to 大家需要注意, 由于我們的上一級是動態 id 路由,所以我們需要進行模板字符串的拼接
并且要使用 v-bind 的方式動態綁定數據,這里簡寫 :to
<router-link :to="`/article/${id}/info1`">info1</router-link> |
- view-router 渲染
其實我們發現,不管是使用一級路由還是二級路由,都是需要這幾步
routes配置, link to 跳轉位置, view-router 渲染內容
編程式導航與路由傳參:
大家發現了嗎,在我們這里的導航都是需要自己去手動的單擊路由入口,我們也可以使用代碼做頁面跳轉,這就是編程式導航,比如說,我們點進去 info2 頁面,過三秒鐘跳轉到 home
我們可以在 info2 界面使用一個 vue 的生命周期函數 created 函數,這是在我們的組件創建完成,但是 Dom 尚未掛載時,可以訪問數據的一個函數。
使用 this.router做跳轉,這里的this.router 做跳轉, 這里的 this.router做跳轉,這里的this.router 并不是我們剛剛開始實例的 router 對象,這個對象是 Vue 幫我們創建的一個代理對象,與原對象隔離。
這里可以使用它做跳轉
<script>export default {name: "info1View",created() {setTimeout(()=>{this.$router.push({path:'/',query:{id: 3}})},3000);}
}
</script><template>
<div>這是子路由info1</div>
</template><style scoped></style>
這里使用 push 跳轉的同時通過 path + query 的方式傳遞參數,等同于在地址上拼接了一個
baseUrl + ?id = 3的方式
這樣攜帶參數的方式有兩種:
// ? 正確:path + query
this.$router.push({path: '/user',query: { id: '123' } // → /user?id=123
})// ?? 錯誤:path + params 會被忽略!
this.$router.push({path: '/user',params: { id: '123' } // params 無效!
})// ? 正確:name + params
this.$router.push({name: 'user',params: { id: '123' } // → /user/123
})
如何獲取攜帶的參數:
我們只需要在組件創建完成的時候,使用 this.$route.query.id接受
對于 this.router和this.router 和 this.router和this.route 這兩個代理實例
我的理解是 this.$router 是包含路由行為的一個實例對象,也就是做跳轉導航方法等
this.$route 這更像是一個路由信息表,存儲我們的一些路由信息
導航守衛:
這里就要使用到 router 了
為了方便起見,我們可以在路由配置文件中,使用 router 對象的 beforEach 做一個導航守衛
其實導航守衛的作用就是可以讓我們在路由跳轉前后進行不同的操作
比如這里在每次路由跳轉前都打印一些路由跳轉信息
這里解釋一些 to from next
to 是去到哪一個路由
from 是從哪一個路由來
next 是放行
這里可以看見我切換路由的信息
通常這個導航守衛是可以做加載動畫,或者權限校驗,對于一些不允許用戶權限訪問的頁面,可以進行攔截
vueX
上一期,我們講了組件之間傳遞參數的用法
- 父傳子 props屬性
- 子傳父 使用this.$emit(自定義事件,返回的數據)
這里有一個問題,就是當我們的需要傳遞的組件層次很深,比如給父組件下子組件的子組件的子組件傳參數,反過來也是,如果一層一層的套 props 或者 emit 事件,是不是很麻煩。
這里就可以用到全局狀態管理了 vuex
當你使用 vuex 管理一個狀態的時候,可以全部組件共享這個狀態
回到項目,我們找到 store/index.js
可以發現這里的代碼
導入 vue ,導入 vuex, 使用 vuex 在 vue 上,這不是和 router/index.js 也就是路由的使用方法類似嗎?
也就是說我們只需要弄懂下面的代碼是什么含義,如何使用,我們就會使用 vuex 做狀態管理了
state
state:存放全局狀態
這是存放全局狀態的地方,你可以在這里定義全局狀態
比如這里定義一個 userLoginState: “未登錄”
現在這個狀態就可以全局訪問了,我們試試
在文章頁面使用:
這樣就能訪問了 this.$store.state.userLoginState
代碼:
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({state: {userLoginState:"未登錄"},getters: {},mutations: {},actions: {},modules: {}
})
Article.vue
<script>
export default {name: 'ArticleView',props:{id:{type:Number,required:true}},methods:{getLoginState(){return this.$store.state.userLoginState;}}
}
</script><template>
<div><div>用戶登錄態:{{getLoginState()}}</div>這是一個文章組件<div>文章id為:{{id}}</div><div><router-link :to="`/article/${id}/info1`">info1</router-link> |<router-link :to="`/article/${id}/info2`">info2</router-link></div><router-view></router-view>
</div>
</template><style scoped></style>
mutations & actions
mutations: 修改全局狀態同步的方式
你說他是一個全局管理狀態,那我為什么一定要使用 mutation 的方式修改了,這是因為通過 mutation 修改的狀態都會被我們的 vue develop 調試工具記錄起來,而且一個全局狀態管理庫,只有通過規定的方式管理才不會很混亂
import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({state: {userLoginState:"未登錄"},getters: {},mutations: {changeLoginState(state,loginState) {state.userLoginState = loginState;}},actions: {},modules: {}
})
我們需要修改可以調用mutation:
actions:修改全局狀態異步的方式(異步邏輯執行完還要調用mutation進行狀態更新)
這里的假設我們是需要幾秒后在修改狀態,這是一個異步執行邏輯, 可以在actions中定義
這里也是使用了mutation中的方法修改,在內部只需要使用 store
在外部調用這個異步方法就是 this.$store.dispatch
因為這里要使用異步,最常見的應用場景就是用戶登錄的時候,我們需要修改用戶登錄態,這個時候,首先會向后端發送請求獲取用戶登錄態,這個請求是一個異步操作。
所以需要使用 await / async 包一層,也就是 promice
getters & modules
getters:獲取state的一些屬性,相當于 computed 計算屬性,緩存計算
這里我們寫了一個測試案例,在 getters 中打印用戶登錄態的字段長度
打開控制臺發現只算了一次
這就是 getters,當你需要獲取這個 state 的一些屬性,并且需要經常調用就可以使用它
modules:模塊化
如果你的項目有多個狀態,你可以每一狀態分一個模塊管理
首先可以在 store 中新建 modules 文件
在modules文件中新建一個 a.js
然后你可以在 a 里面寫上你的狀態,比如這里是一個 count
注意export default 中 namespaced :true 打開命名空間
然后可以在 index.js 中導入這個模塊
a.js
export default {namespaced: true,state:{aCount: 1},mutations:{update(state, count) {state.aCount = count}}
}
index.js
import Vue from 'vue'
import Vuex from 'vuex'
import a from "@/store/modules/a";Vue.use(Vuex)export default new Vuex.Store({state: {userLoginState:"未登錄"},getters: {len(state) {console.log("開始算 state 長度");return state.userLoginState.length;}},mutations: {changeLoginState(state,loginState) {state.userLoginState = loginState;}},actions: {changeLoginStateTime(store,loginState){setTimeout(()=>{store.commit('changeLoginState',loginState);})}},modules: {a: a}
})
最后使用:
這里的使用就多了一個卻別,就是
this.$store.state.模塊.狀態
也就是比原來 this.$store.state.狀態
到這里vuex基本使用就講完了