[圖片上傳失敗...(image-a199bd-1659195036496)]
最近入門 Vue3 并完成 3 個(gè)項(xiàng)目,遇到問(wèn)題蠻多的胳蛮,今天就花點(diǎn)時(shí)間整理一下资柔,和大家分享 15 個(gè)比較常見(jiàn)的問(wèn)題蛮放,基本都貼出對(duì)應(yīng)文檔地址绘证,還請(qǐng)多看文檔~
已經(jīng)完成的 3 個(gè)項(xiàng)目基本都是使用 Vue3 (setup-script 模式)全家桶開(kāi)發(fā)隧膏,因此主要分幾個(gè)方面總結(jié):
- 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 生命周期 | 執(zhí)行時(shí)間說(shuō)明 |
---|---|---|
beforeCreate | setup | 組件創(chuàng)建前執(zhí)行 |
created | setup | 組件創(chuàng)建后執(zhí)行 |
beforeMount | onBeforeMount | 組件掛載到節(jié)點(diǎn)上之前執(zhí)行 |
mounted | onMounted | 組件掛載完成后執(zhí)行 |
beforeUpdate | onBeforeUpdate | 組件更新之前執(zhí)行 |
updated | onUpdated | 組件更新完成之后執(zhí)行 |
beforeDestroy | onBeforeUnmount | 組件卸載之前執(zhí)行 |
destroyed | onUnmounted | 組件卸載完成后執(zhí)行 |
errorCaptured | onErrorCaptured | 當(dāng)捕獲一個(gè)來(lái)自子孫組件的異常時(shí)激活鉤子函數(shù) |
目前 Vue3.x 依然支持 Vue2.x 的生命周期胞枕,但不建議混搭使用,前期可以先使用 2.x 的生命周期魏宽,后面盡量使用 3.x 的生命周期開(kāi)發(fā)腐泻。
由于我使用都是 script-srtup
模式决乎,所以都是直接使用 Vue3.x 的生命周期函數(shù):
// A.vue
<script setup lang="ts">
import { ref, onMounted } from "vue";
let count = ref<number>(0);
onMounted(() => {
count.value = 1;
})
</script>
每個(gè)鉤子的執(zhí)行時(shí)機(jī)點(diǎn),也可以看看文檔:
https://v3.cn.vuejs.org/guide/instance.html#生命周期圖示
2. script-setup 模式中父組件獲取子組件的數(shù)據(jù)
文檔地址:https://v3.cn.vuejs.org/api/sfc-script-setup.html#defineexpose
這里主要介紹父組件如何去獲取子組件內(nèi)部定義的變量贫悄,關(guān)于父子組件通信瑞驱,可以看文檔介紹比較詳細(xì):
https://v3.cn.vuejs.org/guide/component-basics.html
我們可以使用全局編譯器宏的defineExpose
宏,將子組件中需要暴露給父組件獲取的參數(shù)窄坦,通過(guò) {key: vlaue}
方式作為參數(shù)即可,父組件通過(guò)模版 ref 方式獲取子組件實(shí)例凳寺,就能獲取到對(duì)應(yīng)值:
// 子組件
<script setup>
let name = ref("pingan8787")
defineExpose({ name }); // 顯式暴露的數(shù)據(jù)鸭津,父組件才可以獲取
</script>
// 父組件
<Chlid ref="child"></Chlid>
<script setup>
let child = ref(null)
child.value.name //獲取子組件中 name 的值為 pingan8787
</script>
注意:
- 全局編譯器宏只能在 script-setup 模式下使用;
- script-setup 模式下肠缨,使用宏時(shí)無(wú)需
import
可以直接使用逆趋; - script-setup 模式一共提供了 4 個(gè)宏,包括:defineProps晒奕、defineEmits闻书、defineExpose、withDefaults脑慧。
3. 為 props 提供默認(rèn)值
definedProps 文檔:https://v3.cn.vuejs.org/api/sfc-script-setup.html#defineprops-%E5%92%8C-defineemits
withDefaults 文檔: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 個(gè)全局編譯器宏魄眉,還沒(méi)有詳細(xì)介紹,這一節(jié)介紹 defineProps
和 withDefaults
闷袒。
使用 defineProps
宏可以用來(lái)定義組件的入?yún)⒖勇桑褂萌缦拢?/p>
<script setup lang="ts">
let props = defineProps<{
schema: AttrsValueObject;
modelValue: any;
}>();
</script>
這里只定義props
屬性中的 schema
和 modelValue
兩個(gè)屬性的類型, defineProps
的這種聲明的不足之處在于囊骤,它沒(méi)有提供設(shè)置 props 默認(rèn)值的方式晃择。
其實(shí)我們可以通過(guò) withDefaults 這個(gè)宏來(lái)實(shí)現(xiàn):
<script setup lang="ts">
let props = withDefaults(
defineProps<{
schema: AttrsValueObject;
modelValue: any;
}>(),
{
schema: [],
modelValue: ''
}
);
</script>
withDefaults 輔助函數(shù)提供了對(duì)默認(rèn)值的類型檢查,并確保返回的 props 的類型刪除了已聲明默認(rèn)值的屬性的可選標(biāo)志也物。
4. 配置全局自定義參數(shù)
在 Vue2.x 中我們可以通過(guò) 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;
使用時(shí)需要先通過(guò) vue 提供的 getCurrentInstance
方法獲取實(shí)例對(duì)象:
// 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
內(nèi)容輸出如下:
[圖片上傳失敗...(image-682088-1659195036496)]
5. v-model 變化
當(dāng)我們?cè)谑褂?v-model
指令的時(shí)候,實(shí)際上 v-bind
和 v-on
組合的簡(jiǎn)寫(xiě)滑蚯,Vue2.x 和 Vue3.x 又存在差異浪蹂。
- Vue2.x
<ChildComponent v-model="pageTitle" />
<!-- 是以下的簡(jiǎn)寫(xiě): -->
<ChildComponent :value="pageTitle" @input="pageTitle = $event" />
在子組件中,如果要對(duì)某一個(gè)屬性進(jìn)行雙向數(shù)據(jù)綁定膘魄,只要通過(guò) this.$emit('update:myPropName', newValue)
就能更新其 v-model
綁定的值乌逐。
- Vue3.x
<ChildComponent v-model="pageTitle" />
<!-- 是以下的簡(jiǎn)寫(xiě): -->
<ChildComponent :modelValue="pageTitle" @update:modelValue="pageTitle = $event"/>
script-setup
模式下就不能使用 this.$emit
去派發(fā)更新事件,畢竟沒(méi)有 this
创葡,這時(shí)候需要使用前面有介紹到的 defineProps浙踢、defineEmits 兩個(gè)宏來(lái)實(shí)現(xiàn):
// 子組件 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']); // 定義需要派發(fā)的事件名稱
let curValue = ref('');
let props = withDefaults(defineProps<{
modelValue: string;
}>(), {
modelValue: '',
})
onMounted(() => {
// 先將 v-model 傳入的 modelValue 保存
curValue.value = props.modelValue;
})
watch(curValue, (newVal, oldVal) => {
// 當(dāng) curValue 變化,則通過(guò) emit 派發(fā)更新
emit('update:modelValue', newVal)
})
</script>
<template>
<div></div>
</template>
<style lang="scss" scoped></style>
父組件使用的時(shí)候就很簡(jiǎn)單:
// 父組件 father.vue
<script setup lang="ts">
import { ref, onMounted, watch } from "vue";
let curValue = ref('');
watch(curValue, (newVal, oldVal) => {
console.log('[curValue 發(fā)生變化]', newVal)
})
</script>
<template>
<Child v-model='curValue'></Child>
</template>
<style lang="scss" scoped></style>
6. 開(kāi)發(fā)環(huán)境報(bào)錯(cuò)不好排查
文檔地址:https://v3.cn.vuejs.org/api/application-config.html#errorhandler
Vue3.x 對(duì)于一些開(kāi)發(fā)過(guò)程中的異常灿渴,做了更友好的提示警告洛波,比如下面這個(gè)提示:
[圖片上傳失敗...(image-e356a0-1659195036496)]
這樣能夠更清楚的告知異常的出處胰舆,可以看出大概是 <ElInput 0=......
這邊的問(wèn)題,但還不夠清楚蹬挤。
這時(shí)候就可以添加 Vue3.x 提供的全局異常處理器缚窿,更清晰的輸出錯(cuò)誤內(nèi)容和調(diào)用棧信息,代碼如下:
// main.ts
app.config.errorHandler = (err, vm, info) => {
console.log('[全局異常]', err, vm, info)
}
這時(shí)候就能看到輸出內(nèi)容如下:
[圖片上傳失敗...(image-d868b0-1659195036496)]
一下子就清楚很多焰扳。
當(dāng)然倦零,該配置項(xiàng)也可以用來(lái)集成錯(cuò)誤追蹤服務(wù) Sentry 和 Bugsnag。
推薦閱讀:Vue3 如何實(shí)現(xiàn)全局異常處理吨悍?
7. 觀察 ref 的數(shù)據(jù)不直觀扫茅,不方便
當(dāng)我們?cè)诳刂婆_(tái)輸出 ref
聲明的變量時(shí)。
const count = ref<numer>(0);
console.log('[測(cè)試 ref]', count)
會(huì)看到控制臺(tái)輸出了一個(gè) RefImpl
對(duì)象:
[圖片上傳失敗...(image-967761-1659195036496)]
看起來(lái)很不直觀育瓜。我們都知道葫隙,要獲取和修改 ref
聲明的變量的值,需要通過(guò) .value
來(lái)獲取躏仇,所以你也可以:
console.log('[測(cè)試 ref]', count.value);
這里還有另一種方式恋脚,就是在控制臺(tái)的設(shè)置面板中開(kāi)啟 「Enable custom formatters」選項(xiàng)。
[圖片上傳失敗...(image-54ffde-1659195036496)]
[圖片上傳失敗...(image-8f2b2e-1659195036496)]
這時(shí)候你會(huì)發(fā)現(xiàn)焰手,控制臺(tái)輸出的 ref
的格式發(fā)生變化了:
[圖片上傳失敗...(image-42be39-1659195036496)]
更加清晰直觀了糟描。
這個(gè)方法是我在《Vue.js 設(shè)計(jì)與實(shí)現(xiàn)》中發(fā)現(xiàn)的,但在文檔也沒(méi)有找到相關(guān)介紹册倒,如果有朋友發(fā)現(xiàn)了蚓挤,歡迎告知~
二、Vite
1. Vite 動(dòng)態(tài)導(dǎo)入的使用問(wèn)題
使用 webpack 的同學(xué)應(yīng)該都知道驻子,在 webpack 中可以通過(guò) require.context
動(dòng)態(tài)導(dǎo)入文件:
// https://webpack.js.org/guides/dependency-management/
require.context('./test', false, /\.test\.js$/);
在 Vite 中灿意,我們可以使用這兩個(gè)方法來(lái)動(dòng)態(tài)導(dǎo)入文件:
import.meta.glob
該方法匹配到的文件默認(rèn)是懶加載,通過(guò)動(dòng)態(tài)導(dǎo)入實(shí)現(xiàn)崇呵,構(gòu)建時(shí)會(huì)分離獨(dú)立的 chunk缤剧,是異步導(dǎo)入,返回的是 Promise域慷,需要做異步操作荒辕,使用方式如下:
const Components = import.meta.glob('../components/**/*.vue');
// 轉(zhuǎn)譯后:
const Components = {
'./components/a.vue': () => import('./components/a.vue'),
'./components/b.vue': () => import('./components/b.vue')
}
import.meta.globEager
該方法是直接導(dǎo)入所有模塊,并且是同步導(dǎo)入犹褒,返回結(jié)果直接通過(guò) for...in
循環(huán)就可以操作抵窒,使用方式如下:
const Components = import.meta.globEager('../components/**/*.vue');
// 轉(zhuǎn)譯后:
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
}
如果僅僅使用異步導(dǎo)入 Vue3 組件,也可以直接使用 Vue3 defineAsyncComponent API 來(lái)加載:
// 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 類型別名
當(dāng)項(xiàng)目比較復(fù)雜的時(shí)候叠骑,經(jīng)常需要配置 alias 路徑別名來(lái)簡(jiǎn)化一些代碼:
import Home from '@/views/Home.vue'
在 Vite 中配置也很簡(jiǎn)單李皇,只需要在 vite.config.ts
的 resolve.alias
中配置即可:
// vite.config.ts
export default defineConfig({
base: './',
resolve: {
alias: {
"@": path.join(__dirname, "./src")
},
}
// 省略其他配置
})
如果使用的是 TypeScript 時(shí),編輯器會(huì)提示路徑不存在的警告??宙枷,這時(shí)候可以在 tsconfig.json
中添加 compilerOptions.paths
的配置:
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}
3. Vite 配置全局 scss
當(dāng)我們需要使用 scss 配置的主題變量(如 $primary
)掉房、mixin方法(如 @mixin lines
)等時(shí)茧跋,如:
<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 配置文件卓囚,也可以直接寫(xiě)成 scss 代碼:
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
additionalData: '$primary: #993300'
}
}
}
})
三瘾杭、VueRouter
1. script-setup 模式下獲取路由參數(shù)
文檔地址:https://router.vuejs.org/zh/guide/advanced/composition-api.html
由于在 script-setup
模式下,沒(méi)有 this
可以使用哪亿,就不能直接通過(guò) this.$router
或 this.$route
來(lái)獲取路由參數(shù)和跳轉(zhuǎn)路由粥烁。
當(dāng)我們需要獲取路由參數(shù)時(shí),就可以使用 vue-router
提供的 useRoute
方法來(lái)獲取锣夹,使用如下:
// 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; // 獲取參數(shù)
})
</script>
如果要做路由跳轉(zhuǎn)页徐,就可以使用 useRouter
方法的返回值去跳轉(zhuǎn):
const router = useRouter();
router.push({
name: 'search',
query: {/**/},
})
四、Pinia
1. store 解構(gòu)的變量修改后沒(méi)有更新
當(dāng)我們解構(gòu)出 store 的變量后银萍,再修改 store 上該變量的值,視圖沒(méi)有更新:
// 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>
這時(shí)候點(diǎn)擊按鈕觸發(fā) changeName
事件后恤左,視圖上的 name
并沒(méi)有變化贴唇。這是因?yàn)?store 是個(gè) reactive 對(duì)象,當(dāng)進(jìn)行解構(gòu)后飞袋,會(huì)破壞它的響應(yīng)性戳气。所以我們不能直接進(jìn)行解構(gòu)。
這種情況就可以使用 Pinia 提供 storeToRefs
工具方法巧鸭,使用起來(lái)也很簡(jiǎn)單瓶您,只需要將需要解構(gòu)的對(duì)象通過(guò) 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 修改數(shù)據(jù)狀態(tài)的方式
按照官網(wǎng)給的方案,目前有三種方式修改:
- 通過(guò)
store.屬性名
賦值修改單筆數(shù)據(jù)的狀態(tài)郑叠;
這個(gè)方法就是前面一節(jié)使用的:
const changeName = () => {
componentStoreObj.name = 'hello pingan8787';
}
- 通過(guò)
$patch
方法修改多筆數(shù)據(jù)的狀態(tài)夜赵;
文檔地址:https://pinia.vuejs.org/api/interfaces/pinia._StoreWithState.html#patch
當(dāng)我們需要同時(shí)修改多筆數(shù)據(jù)的狀態(tài)時(shí),如果還是按照上面方法乡革,可能要這么寫(xiě):
const changeName = () => {
componentStoreObj.name = 'hello pingan8787'
componentStoreObj.age = '18'
componentStoreObj.addr = 'xiamen'
}
上面這么寫(xiě)也沒(méi)什么問(wèn)題寇僧,但是 Pinia 官網(wǎng)已經(jīng)說(shuō)明,使用 $patch
的效率會(huì)更高沸版,性能更好嘁傀,所以在修改多筆數(shù)據(jù)時(shí),更推薦使用 $patch
视粮,使用方式也很簡(jiǎn)單:
const changeName = () => {
// 參數(shù)類型1:對(duì)象
componentStoreObj.$patch({
name: 'hello pingan8787',
age: '18',
addr: 'xiamen',
})
// 參數(shù)類型2:方法细办,該方法接收 store 中的 state 作為參數(shù)
componentStoreObj.$patch(state => {
state.name = 'hello pingan8787';
state.age = '18';
state.addr = 'xiamen';
})
}
- 通過(guò)
action
方法修改多筆數(shù)據(jù)的狀態(tài);
也可以在 store 中定義 actions 的一個(gè)方法來(lái)更新:
// 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';
}
}
})
使用時(shí):
const changeName = () => {
componentStoreObj.updateState();
}
這三種方式都能更新 Pinia 中 store 的數(shù)據(jù)狀態(tài)馒铃。
五蟹腾、Element Plus
1. element-plus 打包時(shí) @charset 警告
項(xiàng)目新安裝的 element-plus 在開(kāi)發(fā)階段都是正常痕惋,沒(méi)有提示任何警告,但是在打包過(guò)程中娃殖,控制臺(tái)輸出下面警告內(nèi)容:
[圖片上傳失敗...(image-ef80be-1659195036496)]
在官方 issues 中查閱很久:https://github.com/element-plus/element-plus/issues/3219值戳。
嘗試在 vite.config.ts
中配置 charset: false
,結(jié)果也是無(wú)效:
// vite.config.ts
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
charset: false // 無(wú)效
}
}
}
})
最后在官方的 issues 中找到處理方法:
// vite.config.ts
// https://blog.csdn.net/u010059669/article/details/121808645
css: {
postcss: {
plugins: [
// 移除打包element時(shí)的@charset警告
{
postcssPlugin: 'internal:charset-removal',
AtRule: {
charset: (atRule) => {
if (atRule.name === 'charset') {
atRule.remove();
}
}
}
}
],
},
}
2. 中文語(yǔ)言包配置
文檔地址:https://element-plus.gitee.io/zh-CN/guide/i18n.html#%E5%85%A8%E5%B1%80%E9%85%8D%E7%BD%AE
默認(rèn) elemnt-plus 的組件是英文狀態(tài):
[圖片上傳失敗...(image-5fb919-1659195036496)]
我們可以通過(guò)引入中文語(yǔ)言包炉爆,并添加到 ElementPlus 配置中來(lái)切換成中文:
// 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 中文語(yǔ)言包
app.use(ElementPlus, { locale }); // 配置中文語(yǔ)言包
這時(shí)候就能看到 ElementPlus 里面組件的文本變成中文了堕虹。
[圖片上傳失敗...(image-3dabb1-1659195036497)]
總結(jié)
以上是我最近從入門到實(shí)戰(zhàn) Vue3 全家桶的 3 個(gè)項(xiàng)目后總結(jié)避坑經(jīng)驗(yàn),其實(shí)很多都是文檔中有介紹的芬首,只是剛開(kāi)始不熟悉赴捞。也希望大伙多看看文檔咯~
Vue3 script-setup 模式確實(shí)越寫(xiě)越香。
本文內(nèi)容如果有問(wèn)題郁稍,歡迎大家一起評(píng)論討論赦政。