Vue3 實戰(zhàn)筆記 | 快速入門
前言
vue3正式版已經(jīng)發(fā)布好幾個月了。相信有不少人早已躍躍欲試,這里根據(jù)這幾天的項目經(jīng)驗羅列幾點在項目中可能用到的知識點跟大家分享總結(jié),在展開功能點介紹之前,先從一個簡單的demo幫助大家可以快速入手新項目??
案例??
在正式介紹之前,大家可以先跑一下這個 demo 快速熟悉用法
??
????type="primary"?@click="handleClick">{{
??????`${vTitle}${state.nums}-${staticData}`
????}}
????
??????- "(item,?index)?in?state.list"?:key="index">?{{?item?}}?
????
??
效果如下??

vue3生命周期
vue3的生命周期函數(shù)只能用在setup()里使用,變化如下??
| vue2 | vue3 |
|---|---|
| beforeCreate | setup |
| created | setup |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeDestroy | onBeforeUnmount |
| destroyed | onUnmounted |
| activated | onActivated |
| deactivated | onDeactivated |
擴(kuò)展
可以看出來vue2的beforeCreate和created變成了setup
絕大部分生命周期都是在原本vue2的生命周期上帶上了on前綴
使用
在setup中使用生命周期:
import?{??onMounted?}?from?'vue';
export?default?{
??setup()?{
????onMounted(()?=>?{
??????//?在掛載后請求數(shù)據(jù)
??????getList();
????})
??}
};
vue3常用api
上述案例中使用了一些常用的api,下面帶大家一一認(rèn)識下我們的新朋友
setup()
setup函數(shù)是一個新的組件選項。作為在組件內(nèi)使用Composition API的入口點。從生命周期鉤子的視角來看,它會在beforeCreate鉤子之前被調(diào)用,所有變量、方法都在setup函數(shù)中定義,之后return出去供外部使用
該函數(shù)有2個參數(shù):
props
context
其中context是一個上下文對象,具有屬性(attrs,slots,emit,parent,root),其對應(yīng)于vue2中的this.$attrs,this.$slots,this.$emit,this.$parent,this.$root。
setup也用作在tsx中返回渲染函數(shù):
import?{??onMounted?}?from?'vue';
setup(props,?{?attrs,?slots?})?{
????return?()?=>?{
??????const?propsData?=?{?...attrs,?...props?}?as?any;
??????return?{extendSlots(slots)} ;
????};
??},*注意:this關(guān)鍵字在setup()函數(shù)內(nèi)部不可用,在方法中訪問setup中的變量時,直接訪問變量名就可以使用。
擴(kuò)展
為什么props沒有被包含在上下文中?
組件使用props的場景更多,有時甚至只需要使用props
將props獨立出來作為一個參數(shù),可以讓TypeScript對props單獨做類型推導(dǎo),不會和上下文中其他屬性混淆。這也使得setup、render和其他使用了TSX的函數(shù)式組件的簽名保持一致。
reactive, ref
reactive和ref都是vue3中用來創(chuàng)建響應(yīng)式數(shù)據(jù)的api,作用等同于在vue2中的data,不同的是他們使用了ES6的Porxy API解決了vue2?defineProperty?無法監(jiān)聽數(shù)組和對象新增屬性的痛點
用法
??"contain">
????type="primary"?@click="numadd">add
????{{?`${state.str}-${num}`?}}
??
效果如下??

區(qū)別
使用時在setup函數(shù)中需要通過內(nèi)部屬性.value來訪問ref數(shù)據(jù),return出去的ref可直接訪問,因為在返回時已經(jīng)自動解套;reactive可以直接通過創(chuàng)建對象訪問
ref接受一個參數(shù),返回響應(yīng)式ref對象,一般是基本類型值(String?、Nmuber?、Boolean?等)或單值對象。如果傳入的參數(shù)是一個對象,將會調(diào)用?reactive?方法進(jìn)行深層響應(yīng)轉(zhuǎn)換(此時訪問ref中的對象會返回Proxy對象,說明是通過reactive創(chuàng)建的);引用類型值(Object?、Array)使用reactive
toRefs
將傳入的對象里所有的屬性的值都轉(zhuǎn)化為響應(yīng)式數(shù)據(jù)對象(ref)
使用reactive?return 出去的值每個都需要通過reactive對象 .屬性的方式訪問非常麻煩,我們可以通過解構(gòu)賦值的方式范圍,但是直接解構(gòu)的參數(shù)不具備響應(yīng)式,此時可以使用到這個api(也可以對props中的響應(yīng)式數(shù)據(jù)做此處理)
將前面的例子作如下??修改使用起來更加方便:
??"contain">
????type="primary"?@click="numadd">add
-????{{?`${state.str}-${num}`?}}
+????{{?`${str}-${num}`?}}
??
toRef
toRef?用來將引用數(shù)據(jù)類型或者reavtive數(shù)據(jù)類型中的某個值轉(zhuǎn)化為響應(yīng)式數(shù)據(jù)
用法
reactive數(shù)據(jù)類型
/*?reactive數(shù)據(jù)類型?*/
??????let?obj?=?reactive({?name:?'小黃',?sex:?'1'?});
??????let?state?=?toRef(obj,?'name');
??????state.value?=?'小紅';
??????console.log(obj.name);?//?小紅
??????console.log(state.value);?//?小紅
??????obj.name?=?'小黑';
??????console.log(obj.name);?//?小黑
??????console.log(state.value);?//?小黑
引用數(shù)據(jù)類型
??ref----------{{?state1?}}
??type="primary"?@click="handleClick1">change
??
??toRef----------{{?state2?}}
??type="primary"?@click="handleClick2">change
??

小結(jié)
ref?是對原數(shù)據(jù)的拷貝,響應(yīng)式數(shù)據(jù)對象值改變后會同步更新視圖,不會影響到原始值。
toRef?是對原數(shù)據(jù)的引用,響應(yīng)式數(shù)據(jù)對象值改變后不會改變視圖,會影響到原始值。
isRef
判斷是否是ref對象,內(nèi)部是判斷數(shù)據(jù)對象上是否包含__v_isRef屬性且值為true。
setup()?{
??????const?one?=?ref(0);
??????const?two?=?0;
??????const?third?=?reactive({
????????data:?'',
??????});
??????let?four?=?toRef(third,?'data');
??????const?{?data?}?=?toRefs(third);
??????
??????console.log(isRef(one));?//?true
??????console.log(isRef(data));?//?true
??????console.log(isRef(four));?//?true
??????console.log(isRef(two));?//?false
??????console.log(isRef(third));?//?false
????}
unref
如果參數(shù)為ref,則返回內(nèi)部原始值,否則返回參數(shù)本身。內(nèi)部是val = isRef(val) ? val.value : val的語法糖。
setup()?{
??????const?hello?=?ref('hello');
??????console.log(hello);?//?{?__v_isRef:?true,value:?"hello"...?}
??????const?newHello?=?unref(hello);
??????console.log(newHello);?//?hello
????}
watch, watchEffect
watch
watch偵聽器,監(jiān)聽數(shù)據(jù)變化
用法和vue2有些區(qū)別
語法為:watch(source, callback, options)
source:用于指定監(jiān)聽的依賴對象,可以是表達(dá)式,getter函數(shù)或者包含上述兩種類型的數(shù)組(如果要監(jiān)聽多個值)
callback:依賴對象變化后執(zhí)行的回調(diào)函數(shù),帶有2個參數(shù):newVal,oldVal。如果要監(jiān)聽多個數(shù)據(jù)每個參數(shù)可以是數(shù)組?[newVal1, newVal2, ... newValN],[oldVal1, oldVal2, ... oldValN]
options:可選參數(shù),用于配置watch的類型,可以配置的屬性有?immediate(立即觸發(fā)回調(diào)函數(shù))、deep(深度監(jiān)聽)
let?title?=?ref('Create');
??????let?num?=?ref(0);
??????const?state?=?reactive({
????????nums:?0,
????????list:?[],
??????});
??????
??????//?監(jiān)聽ref
??????watch(title,?(newValue,?oldValue)?=>?{
?????????/*?...?*/
??????});
??????//?監(jiān)聽reactive
??????watch(
????????//?getter
????????()?=>?state.list.length,
????????//?callback
????????(v?=?0)?=>?{
??????????state.nums?=?v;
????????},
?????????//?watch?Options
????????{?immediate:?true?}
??????);
??????
??????//?監(jiān)聽多個ref
??????watch([title,?num],?([newTitle,?newNum],?[oldTitle,?oldNum])?=>?{
????????/*?...?*/
??????});??????
??????
??????//?監(jiān)聽reactive多個值
??????watch([()?=>?state.list,?()?=>?state.nums],?([newList,?newNums],?[oldList,?oldvNums])?=>?{
????????/*?...?*/
??????});
我們可以向上面一樣將多個值的監(jiān)聽拆成多個對單個值監(jiān)聽的watch。這有助于我們組織代碼并創(chuàng)建具有不同選項的觀察者;watch方法會返回一個stop()方法,若想要停止監(jiān)聽,便可直接執(zhí)行該stop函數(shù)
watchEffect
立即執(zhí)行傳入的一個函數(shù),并響應(yīng)式追蹤其依賴,并在其依賴變更是重新運(yùn)行該函數(shù).
??"contain">
????type="primary"?@click="numadd">add
????{{?num?}}
??

可以看到在組件初始化的時候該回調(diào)函數(shù)立即執(zhí)行了一次,同時開始自動檢測回調(diào)函數(shù)里頭依賴的值,并在依賴關(guān)系發(fā)生改變時自動觸發(fā)這個回調(diào)函數(shù),這樣我們就不必手動傳入依賴特意去監(jiān)聽某個值了
computed
傳入一個getter函數(shù),返回一個默認(rèn)不可手動修改的ref對象.
setup()?{
??????let?title?=?ref('Create');
??????const?vTitle?=?computed(()?=>?'-'?+?title.value?+?'-');
??????
??????function?handleClick()?{
????????if?(title.value?===?'Create')?{
??????????title.value?=?'Reset';
????????}?else?{
??????????title.value?=?'Create';
????????}
??????}
??????}
反轉(zhuǎn)字符串:
setup()?{
????const?state?=?reactive({
??????value:?'',
??????rvalue:?computed(()?=>
????????state.value
??????????.split('')
??????????.reverse()
??????????.join('')
??????)
????})
????return?toRefs(state)
??}
provide, inject
provide()和inject()用來實現(xiàn)多級嵌套組件之間的數(shù)據(jù)傳遞,父組件或祖先組件使用?provide()向下傳遞數(shù)據(jù),子組件或子孫組件使用inject()來接收數(shù)據(jù)
//?父組件
//?孫組件
getCurrentInstance
getCurrentInstance方法用于獲取當(dāng)前組件實例,僅在setup和生命周期中起作用
import?{?getCurrentInstance,?onBeforeUnmount?}?from?'vue';
const?instance?=?getCurrentInstance();
//?判斷當(dāng)前組件實例是否存在
if?(instance)?{
????onBeforeUnmount(()?=>?{
????????/*?...?*/
?????});
?}
通過instance中的ctx屬性可以獲得當(dāng)前上下文,通過這個屬性可以使用組件實例中的各種全局變量和屬性
$Refs
為了獲得對模板中元素或組件實例的引用,我們可以同樣使用ref并從setup()返回它
??"root">This?is?a?root?element
.sync
在vue2.0中使用.sync實現(xiàn)prop的雙向數(shù)據(jù)綁定,在vue3中將它合并到了v-model里
vue2.0
??????????:current-page.sync="currentPage1"
????>
????
vue3.0
??????????v-model:current-page="currentPage1"
????>
????
v-slot
Child.vue
??"child">
????具名插槽
????"one"?/>
????作用域插槽
????"list"?/>
????具名作用域插槽
????"two"?:data="list"?/>
??
vue2用法
??
????
??????"one">
????????菜單
??????
??????"user">
????????
??????????- "(item,?index)?in?user.data"?:key="index">{{?item?}}
????????
??????
??????"two"?slot-scope="user">
????????{{?user.data?}}
??????
????
??
vue3用法
新指令v-slot統(tǒng)一slot和slot-scope單一指令語法。速記v-slot可以潛在地統(tǒng)一作用域和普通插槽的用法。
??
????
??????
????????菜單
??????
??????"user">
????????
??????????- "(item,?index)?in?user.data"?:key="index">{{?item?}}
????????
??????
??????"user">
????????{{?user.data?}}
??????
??????
??????#two="user">
??????{{?user.data?}}
??????
????
??

Composition API 結(jié)合vuex4, Vue Router 4
createStore,useStore,useRouter,useRoute
在vuex4中通過createStore創(chuàng)建Vuex實例,useStore可以獲取實例,作用等同于vue2.0中的this.$store;
Vue Router 4?中useRouter可以獲取路由器,用來進(jìn)行路由的跳轉(zhuǎn),作用等同于vue2.0的this.$router,useRoute就是鉤子函數(shù)相當(dāng)于vue2.0的this.$route
store/index.ts
import?{createStore}?from?'vuex';
const?store?=?createStore({
??state:?{
????user:?null,
??},
??mutations:?{
????setUser(state,?user)?{
??????state.user?=?user;
????}
??},
??actions:?{},
??modules:?{}
});
router/index.ts
import?{?createRouter,?createWebHashHistory,?RouteRecordRaw?}?from?'vue-router';
import?{?scrollBehavior?}?from?'./scrollBehaviour.ts';
const?routes:?Array?=?[
??{
????path:?'/',
????name:?'Home',
????component:?()?=>?import('/@/views/home.vue')?//?vite.config.vue中配置alias
??}
];
const?router?=?createRouter({
??history:?createWebHashHistory(),
??routes,
??strict:?true,
??scrollBehavior:?scrollBehavior,
});
export?default?router;
main.ts
import?{?createApp?}?from?'vue';
import?App?from?'./App.vue';
import?router?from?'./router';
import?store?from?'./store';
import?{?getTime?}?from?'/@/utils'
const?app?=?createApp(App);
app.config.globalProperties.$getTime?=?getTime?//?vue3配置全局變量,取代vue2的Vue.prototype
app.use(store).use(router)
app.mount('#app');
App.vue
import?{?reactive?}?from?"vue";
import?{?useRouter?}?from?"vue-router";
import?{?useStore?}?from?"vuex";
import?{?ElMessage?}?from?'element-plus';
export?default?{
??name:?"App",
??setup()?{
????const?store?=?useStore();
????const?router?=?useRouter();
????//?用戶名和密碼
????const?Form?=?reactive({
??????username:?"johnYu",
??????password:?"123456",
????});
????//?登錄
????function?handelLogin()?{
??????store.commit("setUser",?{
????????username:?Form.username,
????????password:?Form.password,
??????});
??????ElMessage({
????????type:?'success',
????????message:?'登陸成功',
????????duration:?1500,
??????});
??????//?跳轉(zhuǎn)到首頁
??????router.push({
?????????name:?'Home',
?????????params:?{
???????????username:?Form.username
?????????},
??????});
????}
????return?{
??????Form,
??????handelLogin
??????};
??}
home.vue
import?{?useRouter,?useRoute?}?from?'vue-router';
??import?Breadcrumb?from?'/@/components/Breadcrumb.vue';
??export?default?defineComponent({
????name:?'Home',
????components:?{
??????Breadcrumb,
????},
????setup()?{
??????const?route?=?useRoute();
??????//?接收參數(shù)
??????const?username?=?route.params.username;
??????return?{username}
????}
????})
導(dǎo)航守衛(wèi)
由于使用 Composition API 的原因,setup函數(shù)里面分別使用onBeforeRouteLeave和onBeforeRouteUpdate?兩個新增的 API 代替vue2.0中的beforeRouteLeave和beforeRouteUpdate?。
import?{?onBeforeRouteUpdate,?onBeforeRouteLeave?}?from?'vue-router';
???setup()?{
??????onBeforeRouteUpdate((to)?=>?{
????????if?(to.name?===?'Home'){
????????????/*?...?*/
????????}
??????});
???}
useLink
useLink它提供與router-link的v-slot?API 相同的訪問權(quán)限,將RouterLink的內(nèi)部行為公開為Composition API函數(shù),用于暴露底層的定制能力
??"root">This?is?a?root?element
插槽 prop 的對象包含下面幾個屬性:
href:解析后的 URL。將會作為一個 a 元素的 href attribute。
route:解析后的規(guī)范化的地址。
navigate:觸發(fā)導(dǎo)航的函數(shù)。會在必要時自動阻止事件,和 router-link 同理。
isActive:如果需要應(yīng)用激活的 class 則為 true。允許應(yīng)用一個任意的 class。
isExactActive:如果需要應(yīng)用精確激活的 class 則為 true。允許應(yīng)用一個任意的 class。
擴(kuò)展
樣式 scoped
vue2
/*?深度選擇器?*/
/*方式一:*/
>>>?.foo{?}
/*方式二:*/
/deep/?.foo{?}
/*方式三*/
::v-deep?.foo{?}
vue3
/*?深度選擇器?*/
::v-deep(.foo)?{}
.env環(huán)境擴(kuò)展
vite中的.env文件變量名一定要以VITE_前綴
.env文件
VITE_USE_MOCK?=?true
使用:
import.meta.env.VITE_APP_CONTEXT
使用Composition API替換mixin
眾所周知使用mixin的時候當(dāng)我們一個組件混入大量不同的mixin的時候,會存在兩個非常明顯的問題:命名沖突和數(shù)據(jù)來源不清晰。
每個mixin都可以定義自己的props、data,它們之間是無感的,所以很容易定義相同的變量,導(dǎo)致命名沖突。
另外對組件而言,如果模板中使用不在當(dāng)前組件中定義的變量,那么就會不太容易知道這些變量在哪里定義的,這就是數(shù)據(jù)來源不清晰。
以這個經(jīng)典的Vue 2組件為例,它定義了一個"計數(shù)器"功能:
//counter.js
export?default?{
??data()?{
????return?{
??????count:?0
????};
??},
??methods:?{
????increment()?{
??????this.count++;
????}
??}
}
用法如下:
??
????{{?count?}}
????"increment()">add
??
假設(shè)這邊我們引用了counter和getTime兩個mixin,則無法確認(rèn)count和increment()方法來源,并且兩個mixin中可能會出現(xiàn)重復(fù)命名的概率
下面是使用Composition API定義的完全相同的組件:
//?counter.ts
import?{?ref?}?from?'vue';
export?default?function?()?{
????const?count?=?ref(0);
????function?increment()?{
????????count.value++;
????}
????return?{?count,?increment?};
}
??
????{{?count?}}
????"increment()">add
??
總結(jié)
使用Composition API可以清晰的看到數(shù)據(jù)來源,即使去編寫更多的hook函數(shù),也不會出現(xiàn)命名沖突的問題。??
Composition API 除了在邏輯復(fù)用方面有優(yōu)勢,也會有更好的類型支持,因為它們都是一些函數(shù),在調(diào)用函數(shù)時,自然所有的類型就被推導(dǎo)出來了,不像 Options API 所有的東西使用 this。另外,Composition API 對 tree-shaking 友好,代碼也更容易壓縮。vue3的Composition API會將某個邏輯關(guān)注點相關(guān)的代碼全都放在一個函數(shù)里,這樣當(dāng)需要修改一個功能時,就不再需要在文件中跳來跳去

