1. 程式人生 > 實用技巧 >Vue3.x 從零開始(五)—— Router + Vuex + TypeScript 實戰演練(上)

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 目錄維護基於

vue-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