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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM