Vue3.x 從零開始(五)—— Router + Vuex + TypeScript 實戰演練(上)
前面的幾篇文章已經大致介紹了 Vue 3 的常用 API,現在綜合起來做一個實戰演練
配合完整程式碼食用更香哦,專案地址:https://github.com/wisewrong/test-vue3-demo
一、初始化
首先通過 Vue-CLI 建立一個 Vue 3 專案,詳細流程可以參考《Vue3.x 從零開始(一)》
vue create test-vue3-demo
勾選 TypeScript、Router、Vuex,版本選用 Vue 3.x,其他的選項可以自行選擇,拿不準就直接回車選擇預設
初始化完成後的專案是這樣的:
store 目錄用來維護基於 Vuex 開發的狀態倉庫
router 目錄維護基於
main.ts 是專案的入口檔案,在這裡將 Router 和 Vuex 載入專案中:
二、頭部導航( Router )
首先需要建立頭部導航 <header> 元件,header 屬於公共元件,可以放到 components 目錄下
header 上有 Home 和 About 兩個頁面的導航入口,點選導航可以跳轉到對應的頁面
這個功能可以通過 vue-router 提供的 <router-link> 來實現
<router-link> 是經過封裝的 <a> 標籤,它需要接收一個路由地址 to,類似於 <a> 標籤的 href
<router-link to="/home">Home</router-link>
如果目標路由地址配置了元件,就能在父元件的<router-view> 中渲染對應元件
路由的配置檔案是src/router/index.ts,可以配置路由資訊,包括路由地址和對應的元件
不過 <router-link> 只適合導航選單這種【只需要跳轉頁面,不需要做其他操作】的場景
更多的時候我們需要在函式中進行路由跳轉,這時候可以使用this.$router.push()
三、登入框彈窗(teleport + slot )
首先來完成彈窗元件 <modal>
從業務上來講,這個彈窗元件是在 <header> 上開啟的,也就是說 <header> 會是 <modal> 的父元件
如果按照傳統開發元件的方式,<modal> 會渲染到父元件 <header> 的 DOM 節點下
但從互動的層面來說,彈窗是一個全域性性的強互動,元件應該渲染到外部,比如 body 標籤
在 Vue 3 中提供了一個新的解決方案 teleport
在元件中用 <teleport> 元件包裹需要渲染到外部的模板,然後通過 to 指定渲染的 DOM 節點
<teleport to="body"> <div> <!-- 元件內容 --> </div> </teleport>
to 接收一個可以被 querySelector 識別的字串引數,用於查詢目標 DOM 節點,該 DOM 節點必須在元件外部
這裡將 modal 元件渲染到了body 標籤
上面的程式碼還用到了插槽 <slot>
這個標籤允許父元件向子元件插入自定義的模板內容,在 <modal> 元件中可以讓父元件編輯彈窗的內容
如果元件中需要配置多個 <slot> 標籤,還可以用 name 來給 <slot> 命名
<div class="modal-header"> <slot name="header"> <!-- 這裡是 slot-header 的預設模板 --> <span class="modal-title">{{title}}</span> <button class="modal-close"></button> </slot> </div>
然後在父元件中通過<template v-slot:name> 向指定的 slot 插入內容
<template v-slot:header> <div> 這裡是 slot-header 的內容 </div> </template>
四、完成彈窗表單( $refs + Vuex )
接下來開發登入窗的表單元件 <sign-in-form>
元件的內容十分簡單,就是兩個輸入框 <input />,不多介紹,重點在於獲取表單資料
由於這個 <form> 元件是 <header> 的子元件,所以我們需要在 <header> 獲取 <form> 的資料並提交
Vue 中可以通過 ref 屬性獲取自定義元件的例項
比如上面的程式碼就在 <sign-in-form> 元件上指定了 ref="signInForm"
然後就能在 header 元件中通過 this.$refs.signinForm 獲取到表單元件的例項,並直接使用它的 methods 或者 data
// 沒有找到適合 $ref 的型別斷言,只好用 any const data = (this.$refs.signInForm as any).getValue(); // getValue 是 signInForm 元件中的 methods
現在獲取到了登入資訊,正常來說需要用登入資訊請求登入介面,如果使用者名稱和密碼正確,介面會返回使用者資訊
這裡我們就跳過請求介面的過程,直接把登入資訊當做使用者資訊
使用者資訊對於整個專案來說是一個共用資訊,我們可以選擇暫存在localStorage 或 sessionStorage 中,也可以使用 Vuex 來管理
如果在使用 Vue-CLI 建立專案時勾選了 Vuex,就能在src/store/index.ts中維護公共變數和方法
然後在元件中通過 this.$store 來使用 Vuex 提供的 API
Vuex 中有State、Getter、Mutation、Action、Module 五個核心屬性
其中 State 就像是 Vue 元件中的相應資料 data,Getter類似於計算屬性 computed
然後 Mutation 和Action 都可以看做methods,區別在於:
Mutation 是同步函式,用來更新 state(在嚴格模式下只能通過mutation 來更新 state)
// Vuex const state = { user: 'wise wrong' }; const mutations = { // 所有 mutation 的第一個引數都是 state,後面的引數在呼叫時傳入 updateUser(state, payload) { state.user = payload; } }; // 元件中通過 commit 來呼叫 mutations export default { // ... methods: { handler() { this.$store.commit('updateUser', 'new user'); } }, };
而 Action 可以看做Mutation 的父級,用來提交Mutation,而且可以包含非同步函式
// Vuex const actions = { // action 的第一個引數是 context,其中包含 commit,用來呼叫 mutation fetchUser(context, payload) { fetch('/api', payload) .then((res) => { // 呼叫 mutation context.commit('updateUser', res.data); }) .catch() } } // 元件中通過 dispatch 來呼叫 action export default { // ... methods: { handler() { this.$store.dispatch('fetchUser', {id: 123}); } }, };
回到我們的專案上來,由於我們用的是 TypeScript,所以需要提前定義 state 的型別
// 使用者資訊 export interface UserState { user: string; password: string; } // state 的根型別 export interface RootState { userInfo: UserState; }
然後建立 state.ts 和 mutations.ts
然後在元件中通過 commit 呼叫 mutation 以更新使用者資訊:
由於在 Vuex 4 中刪除了對全域性屬性 $store 的型別支援,所以上面的截圖中 $store 被標紅,程式碼也無法執行
為解決該問題,可以在 src 目錄下建立一個shims-vuex.d.ts檔案,手動宣告 $store
import { Store } from 'vuex'; declare module "@vue/runtime-core" { interface ComponentCustomProperties { $store: Store; } }
但我更推薦使用 Vue 提供的輔助函式 map
對於 state,我們可以在 computed 中使用 mapState 將需要的 state 對映到當前元件
import { mapState } from 'vuex'; export default { // ... computed: { // 元件本身的計算屬性 localComputed () { /* ... */ }, // 使用物件展開運算子將 state 混入當前元件 ...mapState([ 'userInfo', // 以陣列的形式傳入 state 的鍵名 ]) }, methods: { test() { // 以計算屬性的形式使用 state console.log(this.user); } } }
同樣的,對於 mutations 可以通過mapMutations 混入 methods 中
import { mapMutations } from 'vuex'; export default { // ... methods: { // 元件本身的方法 test() { // 以 methods 的形式呼叫 mutation this.updateUserInfo(); }, ...mapMutations([ // 混入名為 updateUserInfo 的 mutation 'updateUserInfo', ]) } }
上面 this.$store.commit 的呼叫方式就可以替換成:
到這裡為止已經具備了一個簡單的 Vue 專案的雛形
接下來會打造一個綜合性的 Todo List,並真正用上 Composition API