大家都知道,現在Vue3的各個版本已經陸續釋出了,并且有很多的團隊已經着手各個庫的開發與Vue2向Vue3的更新,我們當然也不能落後,是以趕緊将跟着本文一起學習新的API吧
一、setup
setup的了解:
Vue3.0的一個配置項,值為一個函數,setup是所有Composition API(組合API)的入口“表演舞台”,元件中所用到的:資料、方法等等,均要配置在setup中。
setup函數有兩種傳回值:
若傳回一個對象,則對象中的屬性、方法, 在模闆中均可以直接使用
若傳回一個渲染函數:則可以自定義渲染内容。
setup使用注意點:
盡量不要與Vue2.x配置混用
setup不能是一個async函數,因為傳回值不再是return的對象, 而是promise, 模闆看不到return對象中的屬性。(後期也可以傳回一個Promise執行個體,但需要Suspense和異步元件的配合)
vue2可以擷取vue3中的屬性和方法,vue3無法擷取vue2中的屬性和方法。
setup函數的使用:
setup 函數是在 created 時候(執行個體被初始化前)執行的。裡邊傳回資料或者方法,插值函數可以檢測到。
因為是執行個體初始化前執行,是以 setup 裡不能使用 this 通路到執行個體。
<template>
<div>
<h3>姓名:{{ name }}</h3>
<h3>年齡:{{ age }}</h3>
<h3>技能:{{ skill }}</h3>
</div>
</template>
<script>
export default {
setup() {
let name = "張三";
let age = 18;
let skill = "犯罪";
return {
name,
age,
skill,
};
},
};
</script>
<style scoped></style>
setup 函數裡有兩個參數,第一個是 props,指元件從父元件那兒接收到的參數,第二個是 context,暴露了 attrs、slot、emit 等實用方法。
二、ref和reactive
上面看着很合理,但是 name 和 age 都是非響應式的,即資料改變并不會觸發渲染。
如何解決問題呢?現在需要響應式的引用:通過 proxy 對資料進行封裝。當資料變化時,觸發模闆等内容的更新。
後面的例子都是通過一秒後修改 name 的值來檢視是否有響應式。如果頁面資料沒有改變,說明沒有響應式。
ref:響應式封裝基本類型資料
使用ref函數,如ref(‘張三’),對基本類型的資料進行封裝,變成proxy({value:‘張三’})這樣一個響應式的資料。
<template>
<div>
<h3>姓名:{{ name }}</h3>
<h3>年齡:{{ age }}</h3>
<h3>技能:{{ skill }}</h3>
</div>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
let name = ref("張三");
let age = ref(18);
let skill = ref("犯罪");
setTimeout(() => {
name.value = "李四",
age.value = 19,
skill.value = "飛刀"
}, 1000);
return {
name,
age,
skill,
};
},
};
</script>
<style scoped>
</style>
改成響應式引用後資料變化,頁面也會發生變化
備注:
接收的資料可以是:基本類型、也可以是對象類型
- 基本類型:響應式依然是靠Object.defineProperty()的get與set完成的
- 對象類型:内部借用了Vue3.0中的一個新函數-reactive函數
(ref處理基本類型用的是get與set處理對象資料用的是es6中的Proxy)
reactive:響應式封裝非基本類型資料
使用reactive函數,将非基本資料類型轉為響應式,一般是對象或者數組。
原理:reactive({name:‘張三’})轉為proxy({name:‘張三’})
<template>
<div>
<h3>姓名:{{ obj.name }}</h3>
<h3>年齡:{{ obj.age }}</h3>
<h3>技能:{{ obj.skill }}</h3>
</div>
</template>
<script>
import { reactive } from "vue";
export default {
setup() {
let obj = reactive({
name: '張三',
age: 18,
skill: '犯罪'
})
setTimeout(() => {
obj.name = '李四',
obj.age = 19,
obj.skill = '飛刀'
})
return {
obj
};
},
};
</script>
<style scoped>
</style>
toRefs
使用過對象解構的同學會覺得插值函數裡的寫法好麻煩啊,是以會用對象解構。但是這裡需要注意的一點,如果直接使用結構後的值,這些值不是響應式的。
解構會将對象或數組解構成普通的資料,丢失了響應。是以解構之前需要用 toRefs 包裹一下,使解構後所有值變成響應式的。
原理:toRefs({ name: "張三", age: 18,skill:"犯罪" }) 轉化成 { name: proxy({ value: "張三" }), age: proxy({ value: 18 }),skill:proxy({value:"犯罪"}) },将内部的所有資料變成響應式的。現在使用解構後就不會丢失響應式了。
<template>
<div>
<h3>姓名:{{ name }}</h3>
<h3>年齡:{{ age }}</h3>
<h3>技能:{{ skill }}</h3>
</div>
</template>
<script>
import { reactive, toRefs } from "vue";
export default {
setup() {
let obj = reactive({
name: "張三",
age: 18,
skill: "犯罪",
});
setTimeout(() => {
obj.name = "李四",
obj.age = 19,
obj.skill = "飛刀";
});
let { name, age, skill } = toRefs(obj);
return {
name,
age,
skill,
};
},
};
</script>
<style scoped>
</style>
reactive對比ref
從定義資料角度對比:
ref用來定義:基本類型資料。
reactive用來定義:對象(或數組)類型資料。
備注:ref也可以用來定義對象(或數組)類型資料,它内部會自動通過reactive轉為代理對象。
從原理角度對比:
ref定義一個基本資料類型是通過Object.defineProperty()的get()與set()來實作響應式(資料劫持)。
ref如果定義的是一個引用資料類型,其底層本質還是reactive,會将我們傳入的值自動轉變為:ref(1) ==> reactive({value:1})
reactive通過使用Proxy來實作響應式(資料劫持),并通過Reflect操作源對象内部的資料。
從使用角度對比:
ref定義的資料:操作資料需要.value,讀取資料時模闆中直接讀取不需要.value。
reactive定義的資料:操作數與讀取資料:均不需要.value。
三、computed和watch
computed函數
與Vue2.X的computed的配置功能一緻
寫法:
<template>
<h1>一個人的資訊</h1>
姓:<input type="text" v-model="person.firstName">
<br>
名:<input type="text" v-model="person.lastName">
<!-- 隻讀 -->
<br>
<!-- <span>全名:{{person.fullName}}</span> -->
全名:<input type="text" v-model="person.fullName">
</template>
<script>
import { reactive, computed } from 'vue'
export default {
name: 'Demo',
setup() {
//資料
let person = reactive({
firstName: '李',
lastName: '四'
})
//計算屬性---簡寫(不考慮計算屬性被修改的情況)
// person.fullName = computed(()=>{
// return person.firstName + '-' +person.lastName
// })
//計算屬性---完整版(考慮讀和寫)
person.fullName = computed({
get() {
return person.firstName + '-' + person.lastName
},
set(value) {
const nameArr = value.split('-')
person.firstName = nameArr[0]
person.lastName = nameArr[1]
}
})
//傳回一個對象
return {
person,
}
}
}
</script>
可以看到,在 Composition API 中,需要先引入 computed 函數,然後正常使用即可。
watch函數
與Vue2.X的配置功能一緻
注意:
監視reactive定義的響應式資料時:oldValue無法正确擷取、強制開啟了深度監視(deep配置失效)
監視reactive定義的響應式資料中的某個屬性(屬性為對象時)時:deep配置有效
<template>
<h2>目前求和為:{{ sum }}</h2>
<button @click="sum++">點我+1</button>
<hr>
<h2>資訊為:{{ msg }}</h2>
<button @click="msg += '!'">修改資訊</button>
<hr>
<h2>一個人的資訊:</h2>
<h2>姓名:{{ person.name }}</h2>
<h2>年齡:{{ person.age }}</h2>
<h3>工資:{{ person.job.j1.salary }}</h3>
<button @click="person.name += '~'">修改姓名</button>
<button @click="person.age++">修改年齡</button>
<button @click="person.job.j1.salary++">漲薪</button>
</template>
<script>
import { ref, reactive, watch } from 'vue'
export default {
name: 'Demo',
setup() {
//資料
let sum = ref(0)
let msg = ref("nihao")
let person = reactive({
name: '李四',
age: 39,
job: {
j1: {
salary: 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)
// },{immediate:true})
//情況三:監視reactive所定義的一個響應式資料的全部屬性
//1.此處無法正确的擷取oldValue
//2.強制開啟了深度監視,且無法關閉(deep配置無效)
// watch(person,(newValue,oldValue)=>{
// console.log('person變了',newValue,oldValue)
// },{immediate:true,deep:false})
//情況四:監視reactive所定義的一個響應式資料的某個屬性
// watch(()=>person.age,(newValue,oldValue)=>{
// console.log('年齡變了',newValue,oldValue)
// },{immediate:true})
//情況四:監視reactive所定義的一個響應式資料的某些屬性
// watch([()=>person.age,()=>person.name],(newValue,oldValue)=>{
// console.log('年齡或姓名變了',newValue,oldValue)
// })
//特殊情況
//此處由于監視的是reactive定義的對象中的某個屬性,是以deep有效
watch(() => person.age, (newValue, oldValue) => {
console.log('年齡變了', newValue, oldValue)
}, { deep: true })
//傳回一個對象
return {
sum,
msg,
person
}
}
}
</script>
<style>
</style>
watchEffect
watchEffect 和 watch 的差別:
隻要寫了 watchEffect,回調就立即執行,沒有惰性
watchEffect 會自己感覺内部的代碼資料是否因為相關依賴發生了改變,如果發生了改變就會執行,是以不需要傳遞很多參數。watch 需要通過參數指定監聽哪個資料。
watchEffect 無法擷取之前的資料和目前的資料。watch 可以擷取之前和目前的資料
watchEffect有點像computed:
但computed注重的是計算出來的值(回調函數的傳回值),是以必須要寫傳回值。
而watchEffect更注重的是過程(回調函數的函數體),是以不用寫傳回值。
watchEffect(() => {
// 當 nameObj.name 發生改變,會觸發 watchEffect 的回調函數
console.log(nameObj.name)
})
暫停watchEffect
例如:5S後結束監聽
const stop = watchEffect(() => {
console.log(nameObj.name)
setTimeout(() => {
stop()
}, 5000)
})
四、生命周期函數
現在用 Composition API 來寫生命周期鈎子和Vue2的生命周期鈎子的對比:
選項式API | 組合式API |
beforeCreate | setup |
created | setup |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
activated | onActivated |
deactivated | onDeactivated |
選項式 API 就是 vue2 的那種 API,因為 vue 元件是通過對象内的配置建立出來的。
因為 setup 函數執行的時間就在 beforeCreate 和 created 之間,是以 Composition API 沒有提供對應的鈎子。
export default {
setup() {
// mounted
onMounted(() => {
console.log('Component is mounted!')
})
}
}
總結
Vue3.X與Vue2.X的差別
1.重構響應式系統,使用Proxy替換Object.defineProperty。
2.使用Proxy優勢:可直接監聽數組類型的資料變化,監聽的目标為對象本身,不需要像Object.defineProperty一樣周遊每個屬性,有一定的性能提升,可攔截apply、ownKeys、has等13種方法,而Object.defineProperty不行,直接實作對象屬性的新增/删除。
3.新增Composition API,更好的邏輯複用和代碼組織。
4.重構 Virtual DOM:模闆編譯時的優化,将一些靜态節點編譯成常量,slot優化,将slot編譯為lazy函數,将slot的渲染的決定權交給子元件,模闆中内聯事件的提取并重用(原本每次渲染都重新生成内聯函數)。
5.代碼結構調整,更便于Tree shaking,使得體積更小。
6.使用Typescript替換Flow。
7.當然vue3.0,完全相容vue2.0,不喜歡新文法的夥伴可以保持使用2.0文法。