Vue3.0相對於Vue2.X的改變
常用Composition API
拉開序幕的setup
-
理解:Vue3.0中一個新的配置項,值為一個函式。
-
setup是所有Composition API(組合API)“表演的舞臺”。
-
元件中所用到的:資料、方法等等,均要配置在setup中。
-
setup函式的兩種返回值:
- 若返回一個物件,則物件中的屬性、方法,在模板中均可以直接使用。(重點關注! )
- 若返回一個渲染函式:則可以自定義渲染內容。(瞭解)
-
注意點:
-
儘量不要與Vue2.x配置混用
- Vue2.x配置(data、methos、computed...)中可以訪問到setup中的屬性、方法。
- 但在setup中不能訪問到Vue2.x配置(data、methos、computed...)。
如果有重名, setup優先。
-
setup不能是一個async函式,因為返回值不再是return的物件,而是promise,模板看不到return物件中的屬性。(後期也可以返回一個Promise例項,但需要Suspense和非同步元件的配合)
-
ref函式
-
作用:定義一個響應式的資料
-
語法:
const xxx = ref(initValue)
- 建立一個包含響應式資料的引用物件(reference物件)。
- JS中操作資料:xxx.value
- 模板中讀取資料:不需要.value,直接:
<div>{{xxx}}</div>
-
備註:
- 接收的資料可以是:基本型別、也可以是物件型別。
- 基本型別的資料:響應式依然是靠
object.defineProperty()
的get
與set
完成的。 - 物件型別的資料:內部“求助”了Vue3.0中的一個新函式——
reactive
函式。
reactive函式
- 作用:定義一個物件型別的響應式資料(基本類型別用它,用ref函式)
- 語法:const代理物件= reactive(被代理物件)接收一個物件(或陣列),返回一個代理器物件(proxy物件)
- reactive定義的響應式資料是"深層次的”。
- 內部基於ES6的 Proxy 實現,通過代理物件操作源物件內部資料都是響應式的
Vue3.0中的響應式原理
vue2.x的響應式
- 實現原理:
- 物件型別:通過
object.defineProperty()
對屬性的讀取、修改進行攔截(資料劫持)。 - 陣列型別:通過重寫更新陣列的一系列方法來實現攔截。(對陣列的變更方法進行了包裹)。
- 物件型別:通過
Object.defineProperty(data,'count',{
get () {},
set () {}
})
- 存在問題:
- 新增屬性、刪除屬性,介面不會更新。
- 直接通過下標修改陣列,介面不會自動更新。
vue3.0的響應式
- 實現原理:
- 通過Proxy(代理):攔截物件中任意屬性的變化,包括:屬性值的讀寫、屬性的新增、屬性的刪除等
- 通過Reflect(反射):對被代理物件的屬性進行操作。
- MDN文件中描述的Proxy與Reflect:
new Proxy(data,{
//攔截讀取屬性值
get (target,prop) {
return Reflect.get(target,prop)
},
//攔截設定屬性值或新增新屬性
set (target, prop, value) {
return Reflect.set(target,prop,value)
},
//攔截刪除屬性
deleteProperty (target,prop) {
return Reflect.deleteProperty(target,prop)
}
})
proxy.name = 'tom'
reactive對比ref
-
從定義資料角度對比:
- ref用來定義:基本型別資料。
- reactive用來定義:物件(或陣列)型別資料。
- 備註:ref也可以用來定義物件(或陣列)型別資料,它內部會自動通過
reactive
轉為代理物件。
-
從原理角度對比:
- ref通過
Object.defineProperty()
的ge
t與set
來實現響應式(資料劫持)。 - reactive通過使用Proxy來實現響應式(資料劫持)﹐並通過Reflect操作源物件內部的資料。
- ref通過
-
從使用角度對比:
- ref定義的資料:操作資料需要
.value
,讀取資料時模板中直接讀取不需要.value
。 - reactive定義的資料:操作資料與讀取資料:均不需要
.value
。
- ref定義的資料:操作資料需要
setup的兩個注意點
-
setup執行的時機
- 在beforeCreate之前執行一次,this是undefined。
-
setup的引數
- props:值為物件,包含:元件外部傳遞過來,且元件內部宣告接收了的屬性。
- context:上下文物件
- attrs:值為物件,包含:元件外部傳遞過來,但沒有在props配置中宣告的屬性,相當於
this.$attrs
- slots:收到的插槽內容,相當於
this.$slots
。 - emit:分發自定義事件的函式,相當於
this.$emit
- attrs:值為物件,包含:元件外部傳遞過來,但沒有在props配置中宣告的屬性,相當於
計算屬性與監視
computed函式
- 與Vue2.x中computed配置功能一致
- 寫法
import {computed} from 'vue'
setup(){
...
//計算屬性--簡寫
let fullName = computed(()=>{
return person.firstName + '-' +person.lastName
})
//計算屬性--完整
let fullName = computed({
get(){
return person.firstName + '-' +person. lastName
},
set(value){
const nameArr = value.split('-')
person.firstName = nameArr[0]
person.lastName = nameArr[1]
}
})
}
watch函式
- Vue2.x中watch配置功能一致
- 兩個注意點:
- 監視
reactive
定義的響應式資料時:oldValue
無法正確獲取、強制開啟了深度監視(deep配置失效) - 監視
reactive
定義的響應式資料中某個屬性時:deep配置有效。
- 監視
setup(){
//資料
let sum = ref(0)
let msg = ref('你好')
let person = ref({
name:'張三',
age:18,
job:{
j1:{
salart:20
}
}
})
}
//情況一:監視ref定義的響應式資料
watch(sum,(newValue,oldValue)=>{
console.log('sum變化了' ,newValue,oldValue)
}, {immediate:true})
//情況二:監視多個ref定義的響應式資料
watch([sum,msg],(newValue,oldValue)=>{
console.log('sum或msg變化了', newValue,oldValue)
})
/* 情況三:監視reactive定義的響應式資料
若watch監視的是reactive定義的響應式資料,則無法正確獲得oldvalue! !
若watch監視的是reactive定義的響應式資料,則強制開啟了深度監視
*/
watch(person, (newValue,oldValue)=>{
console.log('person變化了',newValue,oldValue)
}, {immediate:true,deep:false})//此處的deep配置不再奏效
//情況四:監視reactive定義的響應式資料中的某個屬性
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job變化了',newValue,oldvalue)
},{immediate:true, deep:true})
//情況五:監視reactive所定義的一個響應式資料中的某些屬性
watch([()=>person.name,()=>person.age], (newValue,oldValue)=>{
console.log('person的name或age變化了',newValue, oldValue)
})
//特殊情況
watch(()=>person.job, (newValue,oldValue)=>{
console.log('person的job變化了',newValue,oldValue)
},{deep:true})//此處由於監視的是reactive素定義的物件中的某個屬性,所以deep配置有效
watchEffect函式
- watch的套路是:既要指明監視的屬性,也要指明監視的回撥。
- watchEffect的套路是:不用指明監視哪個屬性,監視的回撥中用到哪個屬性,那就監視哪個屬性。
- watchEffect有點像computed:
- 但
computed
注重的計算出來的值(回撥函式的返回值),所以必須要寫返回值。 - 而
watchEffect
更注重的是過程(回撥函式的函式體),所以不用寫返回值。
- 但
//watchEffect所指定的回撥中用到的資料只要發生變化,則直接重新執行回撥。watchEffect(()=>{
const x1 = sum.value
const x2 = person.age
console.log('watchEffect配置的回撥執行了')
})
生命週期
- Vue3對比Vue2中有改動的生命週期鉤子
beforeDestroy
改名為beforeUnmount
destroyed
改名為unmounted
-
Vue3.0也提供了Composition API形式的生命週期鉤子,與Vue2.x中鉤子對應關係如下:
-
beforeCreate
===>setup()
-
created
========>setup()
-
beforeMount
====>onBeforeMount
-
mounted
========>onMounted
-
beforeUpdate
===>onBeforeUpdate
-
updated
========>onUpdated
-
beforeUnmount
==>onBeforeUnmount
-
unmounted
======>onUnmounted
-
Vue3生命週期圖示:
自定義hook函式
- 什麼是hook?——(本質是一個函式)把setup函式中使用的Composition APl進行了封裝。
- 類似於vue2.x中的mixin。
- 自定義hook的優勢:複用程式碼,讓setup中的邏輯更清楚易懂。
toRef
- 作用:建立一個ref物件,其value值指向另一個物件中的某個屬性值。
- 語法:
const name = toRef(person , 'name')
- 應用:要將響應式物件中的某個屬性單獨提供給外部使用時。
- 擴充套件:
toRefs
與toRef
功能一致,但可以批量建立多個ref物件,語法:toRefs(person)
其它Composition API
shallowReactive與shallowRef
- shallowReactive:只處理物件最外層屬性的響應式(淺響應式)。
- shallowRef:只處理基本資料型別的響應式,不進行物件的響應式處理。
- 什麼時候使用?
- 如果有一個物件資料,結構比較深,但變化時只是外層屬性變化===>
shallowReactive
。 - 如果有一個物件資料,後續功能不會修改該物件中的屬性,而是生新的物件來替換===>
shallowRef
。
- 如果有一個物件資料,結構比較深,但變化時只是外層屬性變化===>
toRaw與markRaw
-
toRaw:
- 作用:將一個由
reactive
生成的響應式物件轉為普通物件。 - 使用場景:用於讀取響應式物件對應的普通物件,對這個普通物件的所有操作,不會引起頁面更新。
- 作用:將一個由
-
markRaw:
- 作用:標記一個物件,使其永遠不會再成為響應式物件。
- 應用場景:
- 有些值不應被設定為響應式的,例如複雜的第三方類庫等。
- 當渲染具有不可變資料來源的大列表時,跳過響應式轉換可以提高效能。
customRef
- 作用:建立一個自定義的ref,並對其依賴項跟蹤和更新觸發進行顯式控制。
- 實現防抖效果:
<template>
<input type="text" v-model="keyword"><h3>{{keyword}}</h3>
</template>
<script>
import {ref,customRef} from 'vue'
export default {
name:'Demo',
setup(){
// let keyword = ref('hello')
//使用vue準備好的內建ref
//自定義一個myRef
function myRef(value,delay){
let timer
//通過customRef去實現自定義
return customRef((track,trigger)=>{
return{
get(){
track()//告訴vue這個value值是需要被“追蹤”的
return value
},
set(newValue){
clearTimeout(timer)
timer = setTimeout(()=>{
value = newValue
trigger()//告訴vue去更新介面
}, delay)
}
}
})
}
let keyWord = myRef('hello',500)//使用程式設計師自定義的ref
return {keyWord}
}
}
</script>
provide與inject
-
作用:實現祖孫元件間通訊
-
套路:父元件有一個
provide
選項來提供資料,後代元件有一個inject
選項來開始使用這些資料 -
具體寫法:
-
祖元件中:
setup(){ ...... let car = reactive({name:'賓士',price:' 40萬'}) provide('car' ,car) ...... }
-
後代元件中:
setup(props,context){ ...... const car = inject('car') return {car} ...... }
-
響應式資料的判斷
- isRef:檢查一個值是否為一個ref物件
- isReactive:檢查一個物件是否是由
reactive
建立的響應式代理 - isReadonly:檢查一個物件是否是由
readonly
建立的只讀代理 - isProxy:檢查一個物件是否是由
reactive
或者readonly
方法建立的代理
Composition API的優勢
Options API存在的問題
使用傳統OptionsAPI中,新增或者修改一個需求,就需要分別在data,methods,computed裡修改。
omposition API的優勢
我們可以更加優雅的組織我們的程式碼,函式。讓相關功能的程式碼更加有序的組織在一起。
新的元件
Fragment
- 在Vue2中:元件必須有一個根標籤
- 在Vue3中:元件可以沒有根標籤,內部會將多個標籤包含在一個Fragment虛擬元素中。好處:減少標籤層級,減小記憶體佔用
Teleport
- 什麼是Teleport?——
Teleport
是一種能夠將我們的元件html結構移動到指定位置的技術。
<teleport to="移動位置">
<div v-if="isShow" class="mask">
<div class="dialog">
<h3>我是一個彈窗</h3>
<button @click="isShow = false">關閉彈窗</ button>
</ div>
</div>
</teleport>
Suspense
- 等待非同步元件時渲染一些額外內容,讓應用有更好的使用者體驗
- 使用步驟:
- 非同步引入元件
import {defineAsyncComponent} from 'vue'
const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
- 使用
Suspense
包裹元件,並配置好default
與fallback
<template>
<div class="app">
<h3>我是App元件</h3>
<Suspense>
<template v-slot:default>
<Child/>
</template>
<template v-slot:fallback>
<h3>載入中.....</h3>
</template>
</Suspense>
</ div>
</template>
其它
全域性API的轉移
- Vue 2.x有許多全域性API和配置。
- 例如:註冊全域性元件、註冊全域性指令等。
//註冊全域性元件
Vue.component('MyButton',{
data:() =>({
count:0
}),
template:'<button @click="count++">Clicked {{ count }} times.</button>'
})
//註冊全域性指令
Vue.directive('focus',{
inserted:el => el.focus()
}
- Vue3.0中對這些API做出了調整:
- 將全域性的API,即:
Vue.xxx
調整到應用例項(app
)上
- 將全域性的API,即:
2.x全域性API( vue ) |
3.x例項API ( app ) |
---|---|
Vue.config.XXXX | app.config.XXXX |
Vue.config.productionTip(生產提示) | 移除 |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
Vue.prototype | app.config.globalProperties |
其它改變
-
data選項應始終被宣告為一個函式。
-
過度類名的更改:
-
Vue2.x寫法
.v-enter, .v-leave-to { opacity:0; } .v-leave, -v-enter-to { opacity: 1; }
-
Vue3.x寫法
-v-enter-from, .v-leave-to { opacity : 0; } .v-leave-from, .v-enter-to { opacity: 1; }
-
-
移除keyCode作為v-on 的修飾符,同時也不再支援
config.keyCodes
-
移除
v-on.native
修飾符-
父元件中繫結事件
<my-component v-on:glose="handleComponentEvent" v-on:click="handleNativeclickEvent" />
-
子元件中宣告自定義事件
<script> export default { emits: ['close'] } </script>
-
移除過濾器(filter)
過濾器雖然這看起來很方便,但它需要一個自定義語法,打破大括號內表示式是“只是JavaScript”的假設
這不僅有學習成本,而且有實現成本!建議用方法呼叫或計算屬性去替換過濾器。