來自公眾號: 前端自習課
鏈接:https://juejin.cn/post/7084536432731095048/
最近入門 Vue3 并完成 3 個項目,遇到問題蠻多的,今天就花點時間整理一下,和大家分享 15 個比較常見的問題,基本都貼出對應文檔地址,還請多看文檔~ 已經完成的 3 個項目基本都是使用 Vue3 (setup- 模式)全家桶開發,因此主要分幾個方面總結:
-
Vue3
-
Vite
-
VueRouter
-
Pinia
-
ElementPlus
文檔地址: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 | Captured | 當捕獲一個來自子孫組件的異常時激活鉤子函數 |
目前 Vue3.x 依然支持 Vue2.x 的生命周期,但不建議混搭使用,前期可以先使用 2.x 的生命周期,后面盡量使用 3.x 的生命周期開發。
由于我使用都是 -srtup模式,所以都是直接使用 Vue3.x 的生命周期函數:
// A.vue
< setup lang="ts">
import { ref, onMounted } from "vue";
let count = ref<number>(0);
onMounted( => {
count.value = 1;
})
</>
每個鉤子的執行時機點,也可以看看文檔:https://v3.cn.vuejs.org/guide/instance.html#生命周期圖示
2. -setup 模式中父組件獲取子組件的數據
文檔地址:https://v3.cn.vuejs.org/api/sfc--setup.html#defineexpose
這里主要介紹父組件如何去獲取子組件內部定義的變量,關于父子組件通信,可以看文檔介紹比較詳細:https://v3.cn.vuejs.org/guide/component-basics.html
我們可以使用 全局編譯器宏的 defineExpose宏,將子組件中需要暴露給父組件獲取的參數,通過 {key: vlaue}方式作為參數即可,父組件通過模版 ref 方式獲取子組件實例,就能獲取到對應值:
// 子組件
< setup>
let name = ref("pingan8787")
defineExpose({ name }); // 顯式暴露的數據,父組件才可以獲取
</>
// 父組件
<Chlid ref="child"></Chlid>
< setup>
let child = ref(null)
child.value.name //獲取子組件中 name 的值為 pingan8787
</>
注意:
-
全局編譯器宏只能在 -setup 模式下使用;
-
-setup 模式下,使用宏時無需 import可以直接使用;
-
-setup 模式一共提供了 4 個宏,包括:defineProps、defineEmits、defineExpose、withDefaults。
definedProps 文檔:https://v3.cn.vuejs.org/api/sfc--setup.html#defineprops-%E5%92%8C-defineemitswithDefaults 文檔:https://v3.cn.vuejs.org/api/sfc--setup.html#%E4%BB%85%E9%99%90-type-%E7%9A%84%E5%8A%9F%E8%83%BD
前面介紹 -setup 模式提供的 4 個 全局編譯器宏,還沒有詳細介紹,這一節介紹 defineProps和 withDefaults。使用 defineProps宏可以用來定義組件的入參,使用如下:
< setup lang="ts">
let props = defineProps<{
schema: AttrsValueObject;
modelValue: any;
}>;
</>
這里只定義props屬性中的 schema和 modelValue兩個屬性的類型, defineProps的這種聲明的不足之處在于,它沒有提供設置 props 默認值的方式。
其實我們可以通過 withDefaults 這個宏來實現:
< setup lang="ts">
let props = withDefaults(
defineProps<{
schema: AttrsValueObject;
modelValue: any;
}>,
{
schema: [],
modelValue: ''
}
);
</>
4. 配置全局自定義參數withDefaults 輔助函數提供了對默認值的類型檢查,并確保返回的 props 的類型刪除了已聲明默認值的屬性的可選標志。
文檔地址: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
constApp = createApp({})
app.config.globalProperties.$api = axios;
app.config.globalProperties.$eventBus = eventBus;
使用時需要先通過 vue 提供的 getCurrentInstance方法獲取實例對象:
// A.vue
< setup lang="ts">
import { ref, onMounted, getCurrentInstance } from "vue";
onMounted( => {
const instance = <any>getCurrentInstance;
const { $api, $eventBus } = instance.appContext.config.globalProperties;
// do something
})
</>
其中 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 :value="pageTitle" @input="pageTitle = $event" />
在子組件中,如果要對某一個屬性進行雙向數據綁定,只要通過 this.$emit('update:myPropName', newValue)就能更新其 v-model綁定的值。
-
Vue3.x
<!-- 是以下的簡寫: -->
<ChildComponent :modelValue="pageTitle" @update:modelValue="pageTitle = $event"/>
-setup模式下就不能使用 this.$emit去派發更新事件,畢竟沒有 this,這時候需要使用前面有介紹到的 defineProps、defineEmits 兩個宏來實現:
// 子組件 child.vue
// 文檔:https://v3.cn.vuejs.org/api/sfc--setup.html#defineprops-%E5%92%8C-defineemits
< 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)
})
</>
<template>
<div></div>
</template>
<style lang="scss" scoped></style>
父組件使用的時候就很簡單:
// 父組件 father.vue
< setup lang="ts">
import { ref, onMounted, watch } from "vue";
let curValue = ref('');
watch(curValue, (newVal, oldVal) => {
console.log('[curValue 發生變化]', newVal)
})
</>
<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聲明的變量時。
constcount = ref<numer>( 0);
console.log( '[測試 ref]', count)
會看到控制臺輸出了一個 RefImpl對象:
看起來很不直觀。我們都知道,要獲取和修改 ref聲明的變量的值,需要通過 .value來獲取,所以你也可以:
console.log( '[測試 ref]', count.value);
這里還有另一種方式,就是在控制臺的設置面板中開啟 「 Enable custom formatters」選項。
image.png
image.png
這時候你會發現,控制臺輸出的 ref的格式發生變化了:
更加清晰直觀了。
二、Vite 1. Vite 動態導入的使用問題這個方法是我在《Vue.js 設計與實現》中發現的,但在文檔也沒有找到相關介紹,如果有朋友發現了,歡迎告知~
文檔地址: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,需要做異步操作,使用方式如下:
constComponents = import.meta.glob( '../components/**/*.vue');
// 轉譯后:
constComponents = {
'./components/a.vue': => import( './components/a.vue'),
'./components/b.vue': => import( './components/b.vue')
}
-
import.meta.globEager
該方法是 直接導入所有模塊,并且是 同步導入,返回結果直接通過 for...in循環就可以操作,使用方式如下:
constComponents = import.meta.globEager( '../components/**/*.vue');
// 轉譯后:
import* as__glob__0_0 from'./components/a.vue'
import* as__glob__0_1 from'./components/b.vue'
constmodules = {
'./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'
constAsyncComp = defineAsyncComponent( =>
import( './components/AsyncComponent.vue')
)
app.component( 'async-component', AsyncComp)
2. Vite 配置 alias 類型別名
文檔地址:https://cn.vitejs.dev/config/#resolve-alias
當項目比較復雜的時候,經常需要配置 alias 路徑別名來簡化一些代碼:
importHome from'@/views/Home.vue'
在 Vite 中配置也很簡單,只需要在 vite.config.ts的 resolve.alias中配置即可:
// vite.config.ts
exportdefaultdefineConfig({
base: './',
resolve: {
alias: {
"@": path.join(__dirname, "./src")
},
}
// 省略其他配置
})
如果使用的是 Type 時,編輯器會提示路徑不存在的警告??,這時候可以在 tsconfig.json中添加 compilerOptions.paths的配置:
{
"compilerOptions": {
"paths": {
"@/*": [ "./src/*"]
}
}
}
3. Vite 配置全局 scss
文檔地址:https://cn.vitejs.dev/config/#css-preprocessoroptions
當我們需要使用 scss 配置的主題變量(如 $primary)、mixin方法(如 @mixin lines)等時,如:
< setup lang="ts">
</>
<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
exportdefaultdefineConfig({
base: './',
css: {
preprocessorOptions: {
// 添加公共樣式
scss: {
additionalData: '@import "./src/style/style.scss";'
}
}
},
plugins: [vue]
// 省略其他配置
})
如果不想使用 scss 配置文件,也可以直接寫成 scss 代碼:
exportdefaultdefineConfig({
css: {
preprocessorOptions: {
scss: {
additionalData: '$primary: #993300'
}
}
}
})
三、VueRouter 1. -setup 模式下獲取路由參數
文檔地址:https://router.vuejs.org/zh/guide/advanced/composition-api.html
由于在 -setup模式下,沒有 this可以使用,就不能直接通過 this.$router或 this.$route來獲取路由參數和跳轉路由。當我們需要獲取路由參數時,就可以使用 vue-router提供的 useRoute方法來獲取,使用如下:
// A.vue
< 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; // 獲取參數
})
</>
如果要做路由跳轉,就可以使用 useRouter方法的返回值去跳轉:
constrouter = useRouter;
router.push({
name: 'search',
query: { /**/},
})
四、Pinia 1. store 解構的變量修改后沒有更新
文檔地址:https://pinia.vuejs.org/core-concepts/#using-the-store
當我們解構出 store 的變量后,再修改 store 上該變量的值,視圖沒有更新:
// A.vue
< setup lang="ts">
import componentStore from "@/store/component";
const componentStoreObj = componentStore;
let { name } = componentStoreObj;
const changeName = => {
componentStoreObj.name = 'hello pingan8787';
}
</>
<template>
<span @click="changeName">{{name}}</span>
</template>
這時候點擊按鈕觸發 changeName事件后,視圖上的 name并沒有變化。這是因為 store 是個 reactive 對象,當進行解構后,會破壞它的響應性。所以我們不能直接進行解構。這種情況就可以使用 Pinia 提供 storeToRefs工具方法,使用起來也很簡單,只需要將需要解構的對象通過 storeToRefs方法包裹,其他邏輯不變:
// A.vue
< 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';
}
</>
<template>
<span @click="changeName">{{name}}</span>
</template>
這樣再修改其值,變更馬上更新視圖了。
2. Pinia 修改數據狀態的方式
按照官網給的方案,目前有三種方式修改:
-
通過 store.屬性名賦值修改單筆數據的狀態;
這個方法就是前面一節使用的:
constchangeName = => {
componentStoreObj.name = 'hello pingan8787';
}
-
通過 $patch方法修改多筆數據的狀態;
文檔地址:https://pinia.vuejs.org/api/interfaces/pinia._StoreWithState.html#patch
當我們需要同時修改多筆數據的狀態時,如果還是按照上面方法,可能要這么寫:
constchangeName = => {
componentStoreObj.name = 'hello pingan8787'
componentStoreObj.age = '18'
componentStoreObj.addr = 'xiamen'
}
上面這么寫也沒什么問題,但是 Pinia 官網已經說明,使用 $patch的效率會更高,性能更好,所以在修改多筆數據時,更推薦使用 $patch,使用方式也很簡單:
constchangeName = => {
// 參數類型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';
})
}
-
通過 action方法修改多筆數據的狀態;
也可以在 store 中定義 actions 的一個方法來更新:
// store.ts
import{ defineStore } from'pinia';
exportdefaultdefineStore({
id: 'testStore',
state: => {
return{
name: 'pingan8787',
age: '10',
addr: 'fujian'
}
},
actions: {
updateState{
this.name = 'hello pingan8787';
this.age = '18';
this.addr = 'xiamen';
}
}
})
使用時:
constchangeName = => {
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
exportdefaultdefineConfig({
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
// ... 省略其他
importElementPlus from'element-plus';
import'element-plus/dist/index.css';
importlocale from'element-plus/lib/locale/lang/zh-cn'; // element-plus 中文語言包
app.use(ElementPlus, { locale }); // 配置中文語言包
這時候就能看到 ElementPlus 里面組件的文本變成中文了。
總結
以上是我最近從入門到實戰 Vue3 全家桶的 3 個項目后總結避坑經驗,其實很多都是文檔中有介紹的,只是剛開始不熟悉。也希望大伙多看看文檔咯~
Vue3 -setup 模式確實越寫越香。
本文內容如果有問題,歡迎大家一起評論討論。
--- EOF ---