1. 程式人生 > 其它 >分享 15 個 Vue3 全家桶開發的避坑經驗

分享 15 個 Vue3 全家桶開發的避坑經驗

原文地址:https://mp.weixin.qq.com/s/6Vd31NkpxzI07oHXZZbWrA

最近入門 Vue3 並完成 3 個專案,遇到問題蠻多的,今天就花點時間整理一下,和大家分享 15 個比較常見的問題,基本都貼出對應文件地址,還請多看文件~ 已經完成的 3 個專案基本都是使用 Vue3 (setup-script 模式)全家桶開發,因此主要分幾個方面總結:

  • Vue3
  • Vite
  • VueRouter
  • Pinia
  • ElementPlus

一、Vue3

1. Vue2.x 和 Vue3.x 生命週期方法的變化

文件地址:https://v3.cn.vuejs.org/guide/composition-api-lifecycle-hooks.html

Vue2.x 和 Vue3.x 生命週期方法的變化蠻大的,先看看:

2.x 生命週期 3.x 生命週期 執行時間說明
beforeCreate setup 元件建立前執行
created setup 元件建立後執行
beforeMount onBeforeMount 元件掛載到節點上之前執行
mounted onMounted 元件掛載完成後執行
beforeUpdate onBeforeUpdate 元件更新之前執行
updated onUpdated 元件更新完成之後執行
beforeDestroy onBeforeUnmount 元件解除安裝之前執行
destroyed onUnmounted 元件解除安裝完成後執行
errorCaptured onErrorCaptured 當捕獲一個來自子孫元件的異常時啟用鉤子函式

目前 Vue3.x 依然支援 Vue2.x 的生命週期,但不建議混搭使用,前期可以先使用 2.x 的生命週期,後面儘量使用 3.x 的生命週期開發。

由於我使用都是 script-srtup模式,所以都是直接使用 Vue3.x 的生命週期函式:

// A.vue
<script setup lang="ts">
import { ref, onMounted } from "vue";
let count = ref<number>(0);

onMounted(() => {
count.value = 1;
})
</script>

每個鉤子的執行時機點,也可以看看文件:https://v3.cn.vuejs.org/guide/instance.html#生命週期圖示

2. script-setup 模式中父元件獲取子元件的資料

文件地址:https://v3.cn.vuejs.org/api/sfc-script-setup.html#defineexpose

這裡主要介紹父元件如何去獲取子元件內部定義的變數,關於父子元件通訊,可以看文件介紹比較詳細:https://v3.cn.vuejs.org/guide/component-basics.html

我們可以使用全域性編譯器巨集defineExpose巨集,將子元件中需要暴露給父元件獲取的引數,通過 {key: vlaue}方式作為引數即可,父元件通過模版 ref 方式獲取子元件例項,就能獲取到對應值:

// 子元件
<script setup>
let name = ref("pingan8787")
defineExpose({ name }); // 顯式暴露的資料,父元件才可以獲取
</script>

// 父元件
<Chlid ref="child"></Chlid>
<script setup>
let child = ref(null)
child.value.name //獲取子元件中 name 的值為 pingan8787
</script>

注意

  • 全域性編譯器巨集只能在 script-setup 模式下使用;
  • script-setup 模式下,使用巨集時無需 import可以直接使用;
  • script-setup 模式一共提供了 4 個巨集,包括:defineProps、defineEmits、defineExpose、withDefaults。

3. 為 props 提供預設值

definedProps 文件:https://v3.cn.vuejs.org/api/sfc-script-setup.html#defineprops-%E5%92%8C-defineemitswithDefaults 文件:https://v3.cn.vuejs.org/api/sfc-script-setup.html#%E4%BB%85%E9%99%90-typescript-%E7%9A%84%E5%8A%9F%E8%83%BD

前面介紹 script-setup 模式提供的 4 個全域性編譯器巨集,還沒有詳細介紹,這一節介紹 defineProps和 withDefaults。使用 defineProps巨集可以用來定義元件的入參,使用如下:

<script setup lang="ts">
let props = defineProps<{
schema: AttrsValueObject;
modelValue: any;
}>();
</script>

這裡只定義props屬性中的 schema和 modelValue兩個屬性的型別, defineProps 的這種宣告的不足之處在於,它沒有提供設定 props 預設值的方式。

其實我們可以通過 withDefaults 這個巨集來實現:

<script setup lang="ts">
let props = withDefaults(
defineProps<{
schema: AttrsValueObject;
modelValue: any;
}>(),
{
schema: [],
modelValue:
}
);
</script>

withDefaults 輔助函式提供了對預設值的型別檢查,並確保返回的 props 的型別刪除了已宣告預設值的屬性的可選標誌。

4. 配置全域性自定義引數

文件地址:https://v3.cn.vuejs.org/guide/migration/global-api.html#vue-prototype-%E6%9B%BF%E6%8D%A2%E4%B8%BA-config-globalproperties

在 Vue2.x 中我們可以通過 Vue.prototype 新增全域性屬性 property。但是在 Vue3.x 中需要將 Vue.prototype 替換為 config.globalProperties配置:

// Vue2.x
Vue.prototype.$api = axios;
Vue.prototype.$eventBus = eventBus;

// Vue3.x
const app = createApp({})
app.config.globalProperties.$api = axios;
app.config.globalProperties.$eventBus = eventBus;

使用時需要先通過 vue 提供的 getCurrentInstance方法獲取例項物件:

// A.vue

<script setup lang="ts">
import { ref, onMounted, getCurrentInstance } from "vue";

onMounted(() => {
const instance = <any>getCurrentInstance();
const { $api, $eventBus } = instance.appContext.config.globalProperties;
// do something
})
</script>

其中 instance內容輸出如下:

5. v-model 變化

文件地址:https://v3.cn.vuejs.org/guide/migration/v-model.html

當我們在使用 v-model指令的時候,實際上 v-bind 和 v-on 組合的簡寫,Vue2.x 和 Vue3.x 又存在差異。

  • Vue2.x
<ChildComponent v-model="pageTitle" />

<!-- 是以下的簡寫: -->
<ChildComponent :value="pageTitle" @input="pageTitle = $event" />

在子元件中,如果要對某一個屬性進行雙向資料繫結,只要通過 this.$emit( update:myPropName , newValue) 就能更新其 v-model繫結的值。

  • Vue3.x
<ChildComponent v-model="pageTitle" />

<!-- 是以下的簡寫: -->

<ChildComponent :modelValue="pageTitle" @update:modelValue="pageTitle = $event"/>

script-setup模式下就不能使用 this.$emit去派發更新事件,畢竟沒有 this,這時候需要使用前面有介紹到的 defineProps、defineEmits 兩個巨集來實現:

// 子元件 child.vue
// 文件:https://v3.cn.vuejs.org/api/sfc-script-setup.html#defineprops-%E5%92%8C-defineemits
<script setup lang="ts">
import { ref, onMounted, watch } from "vue";
const emit = defineEmits([ update:modelValue ]); // 定義需要派發的事件名稱

let curValue = ref( );
let props = withDefaults(defineProps<{
modelValue: string;
}>(), {
modelValue: ,
})

onMounted(() => {
// 先將 v-model 傳入的 modelValue 儲存
curValue.value = props.modelValue;
})

watch(curValue, (newVal, oldVal) => {
// 當 curValue 變化,則通過 emit 派發更新
emit( update:modelValue , newVal)
})

</script>

<template>
<div></div>
</template>

<style lang="scss" scoped></style>

父元件使用的時候就很簡單:

// 父元件 father.vue

<script setup lang="ts">
import { ref, onMounted, watch } from "vue";
let curValue = ref( );

watch(curValue, (newVal, oldVal) => {
console.log( [curValue 發生變化] , newVal)
})
</script>

<template>
<Child v-model= curValue ></Child>
</template>

<style lang="scss" scoped></style>

6. 開發環境報錯不好排查

文件地址:https://v3.cn.vuejs.org/api/application-config.html#errorhandler

Vue3.x 對於一些開發過程中的異常,做了更友好的提示警告,比如下面這個提示:

這樣能夠更清楚的告知異常的出處,可以看出大概是 <ElInput 0=......這邊的問題,但還不夠清楚。這時候就可以新增 Vue3.x 提供的全域性異常處理器,更清晰的輸出錯誤內容和呼叫棧資訊,程式碼如下

// main.ts
app.config.errorHandler = (err, vm, info) => {
    console.log( [全域性異常] , err, vm, info)
}

這時候就能看到輸出內容如下:

一下子就清楚很多。當然,該配置項也可以用來整合錯誤追蹤服務 Sentry 和 Bugsnag。推薦閱讀:Vue3 如何實現全域性異常處理?

7. 觀察 ref 的資料不直觀,不方便

當我們在控制檯輸出 ref宣告的變數時。

const count = ref<numer>(0);

console.log( [測試 ref] , count)

會看到控制檯輸出了一個 RefImpl物件:

看起來很不直觀。我們都知道,要獲取和修改 ref宣告的變數的值,需要通過 .value來獲取,所以你也可以:

console.log( [測試 ref] , count.value);

這裡還有另一種方式,就是在控制檯的設定面板中開啟 「Enable custom formatters」選項。

image.pngimage.png

這時候你會發現,控制檯輸出的 ref的格式發生變化了:

更加清晰直觀了。

這個方法是我在《Vue.js 設計與實現》中發現的,但在文件也沒有找到相關介紹,如果有朋友發現了,歡迎告知~

二、Vite

1. Vite 動態匯入的使用問題

文件地址:https://cn.vitejs.dev/guide/features.html#glob-import

使用 webpack 的同學應該都知道,在 webpack 中可以通過 require.context動態匯入檔案:

// https://webpack.js.org/guides/dependency-management/
require.context( ./test , false, /.test.js$/);

在 Vite 中,我們可以使用這兩個方法來動態匯入檔案:

  • import.meta.glob

該方法匹配到的檔案預設是懶載入,通過動態匯入實現,構建時會分離獨立的 chunk,是非同步匯入,返回的是 Promise,需要做非同步操作,使用方式如下:

const Components = import.meta.glob( ../components/**/*.vue );

// 轉譯後:
const Components = {
   ./components/a.vue : () => import( ./components/a.vue ),
   ./components/b.vue : () => import( ./components/b.vue )
}
  • import.meta.globEager

該方法是直接匯入所有模組,並且是同步匯入,返回結果直接通過 for...in迴圈就可以操作,使用方式如下:

const Components = import.meta.globEager( ../components/**/*.vue );

// 轉譯後:
import * as __glob__0_0 from  ./components/a.vue
import * as __glob__0_1 from  ./components/b.vue
const modules = {
   ./components/a.vue : __glob__0_0,
   ./components/b.vue : __glob__0_1
}

如果僅僅使用非同步匯入 Vue3 元件,也可以直接使用 Vue3 defineAsyncComponent API 來載入:

// https://v3.cn.vuejs.org/api/global-api.html#defineasynccomponent

import { defineAsyncComponent } from  vue

const AsyncComp = defineAsyncComponent(() =>
  import( ./components/AsyncComponent.vue )
)

app.component( async-component , AsyncComp)

2. Vite 配置 alias 類型別名

文件地址:https://cn.vitejs.dev/config/#resolve-alias

當專案比較複雜的時候,經常需要配置 alias 路徑別名來簡化一些程式碼:

import Home from  @/views/Home.vue 

在 Vite 中配置也很簡單,只需要在 vite.config.ts 的 resolve.alias中配置即可:

// vite.config.ts
export default defineConfig({
  base:  ./ ,
  resolve: {
    alias: {
      "@": path.join(__dirname, "./src")
    },
  }
  // 省略其他配置
})

如果使用的是 TypeScript 時,編輯器會提示路徑不存在的警告⚠️,這時候可以在 tsconfig.json中新增 compilerOptions.paths的配置:

{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
     }
  }
}

3. Vite 配置全域性 scss

文件地址:https://cn.vitejs.dev/config/#css-preprocessoroptions

當我們需要使用 scss 配置的主題變數(如 $primary)、mixin方法(如 @mixin lines)等時,如:

<script setup lang="ts">
</script>
<template>
<div class="container"></div>
</template>

<style scoped lang="scss">
.container{
color: $primary;
@include lines;
}
</style>

我們可以將 scss 主題配置檔案,配置在 vite.config.ts 的 css.preprocessorOptions.scss.additionalData中:

// vite.config.ts
export default defineConfig({
  base:  ./ ,
  css: {
    preprocessorOptions: {
      // 新增公共樣式
      scss: {
        additionalData:  @import "./src/style/style.scss";
      }

    }
  },
  plugins: [vue()]
  // 省略其他配置
})

如果不想使用 scss 配置檔案,也可以直接寫成 scss 程式碼:

export default defineConfig({
  css: {
    preprocessorOptions: {
      scss: {
        additionalData:  $primary: #993300
      }
    }
  }
})

三、VueRouter

1. script-setup 模式下獲取路由引數

文件地址:https://router.vuejs.org/zh/guide/advanced/composition-api.html

由於在 script-setup模式下,沒有 this可以使用,就不能直接通過 this.$router或 this.$route來獲取路由引數和跳轉路由。當我們需要獲取路由引數時,就可以使用 vue-router提供的 useRoute方法來獲取,使用如下:

// A.vue

<script setup lang="ts">
import { ref, onMounted } from vue ;
import router from "@/router";

import { useRoute } from vue-router

let detailId = ref<string>( );

onMounted(() => {
const route = useRoute();
detailId.value = route.params.id as string; // 獲取引數
})
</script>

如果要做路由跳轉,就可以使用 useRouter方法的返回值去跳轉:

const router = useRouter();
router.push({
  name:  search ,
  query: {/**/},
})

四、Pinia

1. store 解構的變數修改後沒有更新

文件地址:https://pinia.vuejs.org/core-concepts/#using-the-store

當我們解構出 store 的變數後,再修改 store 上該變數的值,檢視沒有更新:

// A.vue
<script setup lang="ts">
import componentStore from "@/store/component";
const componentStoreObj = componentStore();

let { name } = componentStoreObj;

const changeName = () => {
componentStoreObj.name = hello pingan8787 ;
}
</script>

<template>
<span @click="changeName">{{name}}</span>
</template>

這時候點選按鈕觸發 changeName事件後,檢視上的 name 並沒有變化。這是因為 store 是個 reactive 物件,當進行解構後,會破壞它的響應性。所以我們不能直接進行解構。這種情況就可以使用 Pinia 提供 storeToRefs工具方法,使用起來也很簡單,只需要將需要解構的物件通過 storeToRefs方法包裹,其他邏輯不變:

// A.vue
<script setup lang="ts">
import componentStore from "@/store/component";
import { storeToRefs } from pinia ;
const componentStoreObj = componentStore();

let { name } = storeToRefs(componentStoreObj); // 使用 storeToRefs 包裹

const changeName = () => {
componentStoreObj.name = hello pingan8787 ;
}
</script>

<template>
<span @click="changeName">{{name}}</span>
</template>

這樣再修改其值,變更馬上更新檢視了。

2. Pinia 修改資料狀態的方式

按照官網給的方案,目前有三種方式修改:

  1. 通過 store.屬性名賦值修改單筆資料的狀態;

這個方法就是前面一節使用的:

const changeName = () => {
  componentStoreObj.name =  hello pingan8787 ;
}
  1. 通過 $patch方法修改多筆資料的狀態;

文件地址:https://pinia.vuejs.org/api/interfaces/pinia._StoreWithState.html#patch

當我們需要同時修改多筆資料的狀態時,如果還是按照上面方法,可能要這麼寫:

const changeName = () => {
  componentStoreObj.name =  hello pingan8787
  componentStoreObj.age =  18
  componentStoreObj.addr =  xiamen
}

上面這麼寫也沒什麼問題,但是 Pinia 官網已經說明,使用 $patch的效率會更高,效能更好,所以在修改多筆資料時,更推薦使用 $patch,使用方式也很簡單:

const changeName = () => {
  // 引數型別1:物件
  componentStoreObj.$patch({
    name:  hello pingan8787 ,
    age:  18 ,
    addr:  xiamen ,
  })
  
  // 引數型別2:方法,該方法接收 store 中的 state 作為引數
  componentStoreObj.$patch(state => {
    state.name =  hello pingan8787 ;
    state.age =  18 ;
    state.addr =  xiamen ;
  })
}
  1. 通過 action方法修改多筆資料的狀態;

也可以在 store 中定義 actions 的一個方法來更新:

// store.ts
import { defineStore } from  pinia ;

export default defineStore({
    id:  testStore ,
    state: () => {
        return {
            name:  pingan8787 ,
            age:  10 ,
            addr:  fujian
        }
    },
    actions: {
        updateState(){
            this.name =  hello pingan8787 ;
            this.age =  18 ;
            this.addr =  xiamen ;
        }
    }
})

使用時:

const changeName = () => {
  componentStoreObj.updateState();
}

這三種方式都能更新 Pinia 中 store 的資料狀態。

五、Element Plus

1. element-plus 打包時 @charset 警告

專案新安裝的 element-plus 在開發階段都是正常,沒有提示任何警告,但是在打包過程中,控制檯輸出下面警告內容:

在官方 issues 中查閱很久:https://github.com/element-plus/element-plus/issues/3219。

嘗試在 vite.config.ts中配置 charset: false,結果也是無效:

// vite.config.ts
export default defineConfig({
  css: {
    preprocessorOptions: {
      scss: {
        charset: false // 無效
      }
    }
  }
})

最後在官方的 issues 中找到處理方法:

// vite.config.ts

// https://blog.csdn.net/u010059669/article/details/121808645
css: {
  postcss: {
    plugins: [
      // 移除打包element時的@charset警告
      {
        postcssPlugin:  internal:charset-removal ,
        AtRule: {
          charset: (atRule) => {
            if (atRule.name ===  charset ) {
              atRule.remove();
            }
          }
        }
      }
    ],
  },
}

2. 中文語言包配置

文件地址:https://element-plus.gitee.io/zh-CN/guide/i18n.html#%E5%85%A8%E5%B1%80%E9%85%8D%E7%BD%AE

預設 elemnt-plus 的元件是英文狀態:

我們可以通過引入中文語言包,並新增到 ElementPlus 配置中來切換成中文:

// main.ts

// ... 省略其他
import ElementPlus from  element-plus ;
import  element-plus/dist/index.css ;
import locale from  element-plus/lib/locale/lang/zh-cn ; // element-plus 中文語言包

app.use(ElementPlus, { locale }); // 配置中文語言包

這時候就能看到 ElementPlus 裡面元件的文字變成中文了。

總結

以上是我最近從入門到實戰 Vue3 全家桶的 3 個專案後總結避坑經驗,其實很多都是文件中有介紹的,只是剛開始不熟悉。也希望大夥多看看文件咯~

Vue3 script-setup 模式確實越寫越香。

本文內容如果有問題,歡迎大家一起評論討論。

作者: pingan8787

https://segmentfault.com/a/1190000041679573