Composition API
也叫組合式API,是Vue3.x的新特性。
通過建立 Vue 元件,我們可以將介面的可重複部分及其功能提取到可重用的程式碼段中。僅此一項就可以使我們的應用程式在可維護性和靈活性方面走得更遠。然而,我們的經驗已經證明,光靠這一點可能是不夠的,尤其是當你的應用程式變得非常大的時候——想想幾百個元件。在處理如此大的應用程式時,共享和重用程式碼變得尤為重要
通俗的講:
沒有Composition API
之前vue相關業務的程式碼需要配置到option的特定的區域,中小型專案是沒有問題的,但是在大型專案中會導致後期的維護性比較複雜,同時程式碼可複用性不高。Vue3.x中的composition-api就是為了解決這個問題而生的
compositon api提供了以下幾個函式:
setup
ref
reactive
watchEffect
watch
computed
toRefs
- 生命週期的
hooks
一、setup元件選項
新的setup
元件選項在建立元件之前執行,一旦props
被解析,並充當合成API
的入口點
提示:
由於在執行setup
時尚未建立元件例項,因此在setup
選項中沒有this
。這意味著,除了props
之外,你將無法訪問元件中宣告的任何屬性——本地狀態、計算屬性或方法。
使用 setup
函式時,它將接受兩個引數:
props
context
讓我們更深入地研究如何使用每個引數
1. Props
setup
函式中的第一個引數是props
。正如在一個標準元件中所期望的那樣,setup
函式中的props
是響應式的,當傳入新的prop
時,它將被更新
// MyBook.vue
export default {
props: {
title: String
},
setup(props) {
console.log(props.title)
}
}
注意:
但是,因為props
是響應式的,你不能使用ES6
解構,因為它會消除prop
的響應性。
如果需要解構 prop,可以通過使用 setup
函式中的 toRefs
來安全地完成此操作。
// MyBook.vue
import { toRefs } from 'vue'
setup(props) {
const { title } = toRefs(props)
console.log(title.value)
}
2. 上下文
傳遞給setup
函式的第二個引數是context
。context
是一個普通的 JavaScript 物件,它暴露三個元件的 property
// MyBook.vue
export default {
setup(props, context) {
// Attribute (非響應式物件)
console.log(context.attrs)
// 插槽 (非響應式物件)
console.log(context.slots)
// 觸發事件 (方法)
console.log(context.emit)
}
}
context
是一個普通的 JavaScript 物件,也就是說,它不是響應式的,這意味著你可以安全地對context
使用 ES6 解構
// MyBook.vue
export default {
setup(props, { attrs, slots, emit }) {
...
}
}
attrs
和slots
是有狀態的物件,它們總是會隨元件本身的更新而更新。這意味著你應該避免對它們進行解構,並始終以attrs.x
或slots.x
的方式引用 property。請注意,與props
不同,attrs
和slots
是非響應式的。如果你打算根據attrs
或slots
更改應用副作用,那麼應該在onUpdated
生命週期鉤子中執行此操作。
3. setup元件的 property
執行 setup
時,元件例項尚未被建立。因此,你只能訪問以下 property:
props
attrs
slots
emit
換句話說,你將無法訪問以下元件選項:
data
computed
methods
4. ref reactive 以及setup結合模板使用
在看setup
結合模板使用之前,我們首先得知道ref
和 reactive
方法。
如果 setup
返回一個物件則可以在模板中繫結物件中的屬性和方法,但是要定義響應式資料的時候可以使用ref
, reactive
方法定義響應式的資料
錯誤寫法:
<template>
{{msg}}
<br>
<button @click="updateMsg">改變etup中的msg</button>
<br>
</template>
<script>
export default {
data() {
return {
}
},
setup() {
let msg = "這是setup中的msg";
let updateMsg = () => {
alert("觸發方法")
msg = "改變後的值"
}
return {
msg,
updateMsg
}
},
}
</script>
<style lang="scss">
.home {
position: relative;
}
</style>
正確寫法一:
ref用來定義響應式的 字串、 數值、 陣列、Bool
型別
import {
ref
} from 'vue'
<template>
{{msg}}
<br>
<br>
<button @click="updateMsg">改變etup中的msg</button>
<br>
<br>
<ul>
<li v-for="(item,index) in list" :key="index">
{{item}}
</li>
</ul>
<br>
</template>
<script>
import {
ref
} from 'vue'
export default {
data() {
return {
}
},
setup() {
let msg = ref("這是setup中的msg");
let list = ref(["馬總", "李總", "劉總"])
let updateMsg = () => {
alert("觸發方法");
msg.value = "改變後的值"
}
return {
msg,
list,
updateMsg
}
},
}
</script>
<style lang="scss">
.home {
position: relative;
}
</style>
正確寫法二:
reactive 用來定義響應式的物件
import {
reactive
} from 'vue'
<template>
{{msg}}
<br>
<br>
<button @click="updateMsg">改變setup中的msg</button>
<br>
<br>
<ul>
<li v-for="(item,index) in list" :key="index">
{{item}}
</li>
</ul>
<br>
{{setupData.title}}
<br>
<button @click="updateTitle">更新setup中的title</button>
<br>
<br>
</template>
<script>
import {
reactive,
ref
} from 'vue'
export default {
data() {
return {
}
},
setup() {
let msg = ref("這是setup中的msg");
let setupData = reactive({
title: "reactive定義響應式資料的title",
userinfo: {
username: "張三",
age: 20
}
})
let updateMsg = () => {
alert("觸發方法");
msg.value = "改變後的值"
}
let updateTitle = () => {
alert("觸發方法");
setupData.title = "我是改變後的title"
}
return {
msg,
setupData,
updateMsg,
updateTitle
}
},
}
</script>
<style lang="scss">
.home {
position: relative;
}
</style>
說明:要改變ref定義的屬性名稱需要通過 屬性名稱.value
來修改,要改變reactive
中定義的物件名稱可以直接
5. 使用 this
在setup()
內部,this
不會是該活躍例項的引用,因為setup()
是在解析其它元件選項之前被呼叫的,所以setup()
內部的this
的行為與其它選項中的this
完全不同。這在和其它選項式 API 一起使用setup()
時可能會導致混淆
二、toRefs - 解構響應式物件資料
把一個響應式物件轉換成普通物件,該普通物件的每個property
都是一個ref
,和響應式物件property
一一對應
<template>
<div>
<h1>解構響應式物件資料</h1>
<p>Username: {{username}}</p>
<p>Age: {{age}}</p>
</div>
</template>
<script>
import {
reactive,
toRefs
} from "vue";
export default {
name: "解構響應式物件資料",
setup() {
const user = reactive({
username: "張三",
age: 10000,
});
return {
...toRefs(user)
};
},
};
</script>
當想要從一個組合邏輯函式中返回響應式物件時,用 toRefs 是很有效的,該 API 讓消費元件可以 解構 / 擴充套件(使用 …
操作符)返回的物件,並不會丟失響應性:
function useFeatureX() {
const state = reactive({
foo: 1,
bar: 2,
})
// 對 state 的邏輯操作
// ....
// 返回時將屬性都轉為 ref
return toRefs(state)
}
export default {
setup() {
// 可以解構,不會丟失響應性
const { foo, bar } = useFeatureX()
return {
foo,
bar,
}
},
}
三、computed - 計算屬性
<template>
<div>
<h1>解構響應式物件資料+computed</h1>
<input type="text" v-model="firstName" placeholder="firstName" />
<br>
<br>
<input type="text" v-model="lastName" placeholder="lastName" />
<br>
{{fullName}}
</div>
</template>
<script>
import {
reactive,
toRefs,
computed
} from "vue";
export default {
name: "解構響應式物件資料",
setup() {
const user = reactive({
firstName: "",
lastName: "",
});
const fullName = computed(() => {
return user.firstName + " " + user.lastName
})
return {
...toRefs(user),
fullName
};
},
};
</script>
四、readonly “深層”的只讀代理
傳入一個物件(響應式或普通)或 ref,返回一個原始物件的只讀代理。一個只讀的代理是“深層的”,物件內部任何巢狀的屬性也都是隻讀的
<template>
<div>
<h1>readonly - “深層”的只讀代理</h1>
<p>original.count: {{original.count}}</p>
<p>copy.count: {{copy.count}}</p>
</div>
</template>
<script>
import { reactive, readonly } from "vue";
export default {
name: "Readonly",
setup() {
const original = reactive({ count: 0 });
const copy = readonly(original);
setInterval(() => {
original.count++;
copy.count++; // 報警告,Set operation on key "count" failed: target is readonly. Proxy {count: 1}
}, 1000);
return { original, copy };
},
};
</script>
五、watchEffect
在響應式地跟蹤其依賴項時立即執行一個函式,並在更改依賴項時重新執行它。
<template>
<div>
<h1>watchEffect - 偵聽器</h1>
<p>{{data.count}}</p>
<button @click="stop">手動關閉偵聽器</button>
</div>
</template>
<script>
import {
reactive,
watchEffect
} from "vue";
export default {
name: "WatchEffect",
setup() {
const data = reactive({
count: 1,
num: 1
});
const stop = watchEffect(() => console.log(`偵聽器:${data.count}`));
setInterval(() => {
data.count++;
}, 1000);
return {
data,
stop
};
},
};
</script>
六、watch 、watch 與watchEffect區別
對比watchEffect
,watch
允許我們:
- 懶執行,也就是說僅在偵聽的源變更時才執行回撥;
- 更明確哪些狀態的改變會觸發偵聽器重新執行;
- 訪問偵聽狀態變化前後的值
更明確哪些狀態的改變會觸發偵聽器重新執行
<template>
<div>
<h1>watch - 偵聽器</h1>
<p>count1: {{data.count1}}</p>
<p>count2: {{data.count2}}</p>
<button @click="stopAll">Stop All</button>
</div>
</template>
<script>
import {
reactive,
watch
} from "vue";
export default {
name: "Watch",
setup() {
const data = reactive({
count1: 0,
count2: 0
});
// 偵聽單個資料來源
const stop1 = watch(data, () =>
console.log("watch1", data.count1, data.count2)
);
// 偵聽多個資料來源
const stop2 = watch([data], () => {
console.log("watch2", data.count1, data.count2);
});
setInterval(() => {
data.count1++;
}, 1000);
return {
data,
stopAll: () => {
stop1();
stop2();
},
};
},
};
</script>
訪問偵聽狀態變化前後的值
<template>
<div>
<h1>watch - 偵聽器</h1>
<input type="text" v-model="keywords" />
</div>
</template>
<script>
import {
ref,
watch
} from "vue";
export default {
name: "Watch",
setup() {
let keywords = ref("111");
// 偵聽單個資料來源
watch(keywords, (newValue, oldValue) => {
console.log(newValue, oldValue)
});
return {
keywords
};
},
};
</script>
懶執行,也就是說僅在偵聽的源變更時才執行回撥
<template>
<div>
<h1>watch - 偵聽器</h1>
<p>num1={{num1}}</p>
<p>num2={{num2}}</p>
</div>
</template>
<script>
import {
ref,
watch,
watchEffect
} from "vue";
export default {
name: "Watch",
setup() {
let num1 = ref(10);
let num2 = ref(10);
// 偵聽單個資料來源
watch(num1, (newValue, oldValue) => {
console.log(newValue, oldValue)
});
watchEffect(() => console.log(`watchEffect偵聽器:${num2.value}`));
return {
num1,
num2
};
},
};
</script>
七、組合式api生命週期鉤子
你可以通過在生命週期鉤子前面加上 “on” 來訪問元件的生命週期鉤子。
下表包含如何在 setup () 內部呼叫生命週期鉤子:
選項式 API | Hook inside setup |
---|---|
beforeCreate | 不需要* |
created | 不需要* |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
因為setup
是圍繞beforeCreate
和created
生命週期鉤子執行的,所以不需要顯式地定義它們。換句話說,在這些鉤子中編寫的任何程式碼都應該直接在setup
函式中編寫
export default {
setup() {
// mounted
onMounted(() => {
console.log('Component is mounted!')
})
}
}
八、Provider Inject
通常,當我們需要將資料從父元件傳遞到子元件時,我們使用 props。想象一下這樣的結構:你有一些深巢狀的元件,而你只需要來自深巢狀子元件中父元件的某些內容。在這種情況下,你仍然需要將 prop 傳遞到整個元件鏈中,這可能會很煩人
對於這種情況,我們可以使用provide
和inject
對父元件可以作為其所有子元件的依賴項提供程式,而不管元件層次結構有多深。這個特性有兩個部分:父元件有一個provide
選項來提供資料,子元件有一個inject
選項來開始使用這個資料
1. 非組合式api中的寫法
<!-- src/components/MyMap.vue -->
<template>
<MyMarker />
</template>
<script>
import MyMarker from './MyMarker.vue'
export default {
components: {
MyMarker
},
provide: {
location: 'North Pole',
geolocation: {
longitude: 90,
latitude: 135
}
}
}
</script>
<!-- src/components/MyMarker.vue -->
<script>
export default {
inject: ['location', 'geolocation']
}
</script>
2. 組合式api中的寫法
Provider:
在setup()
中使用provide
時,我們首先從vue
顯式匯入provide
方法。這使我們能夠呼叫provide
時來定義每個property
provide
函式允許你通過兩個引數定義 property
:
property
的name
(<String>
型別)property
的value
使用 MyMap
元件,我們提供的值可以按如下方式重構:
<!-- src/components/MyMap.vue -->
<template>
<MyMarker />
</template>
<script>
import { provide } from 'vue'
import MyMarker from './MyMarker.vue
export default {
components: {
MyMarker
},
setup() {
provide('location', 'North Pole')
provide('geolocation', {
longitude: 90,
latitude: 135
})
}
}
</script>
Inject:
在setup()
中使用inject
時,還需要從vue
顯式匯入它。一旦我們這樣做了,我們就可以呼叫它來定義如何將它暴露給我們的元件。
inject
函式有兩個引數:
- 要注入的
property
的名稱 - 一個預設的值 (可選)
使用 MyMarker
元件,可以使用以下程式碼對其進行重構:
<!-- src/components/MyMarker.vue -->
<script>
import { inject } from 'vue'
export default {
setup() {
const userLocation = inject('location', 'The Universe')
const userGeolocation = inject('geolocation')
return {
userLocation,
userGeolocation
}
}
}
</script>
Provider Inject 響應性
父元件:
import {
provide,
ref,
reactive
} from 'vue'
setup() {
const location = ref('北京')
const geolocation = reactive({
longitude: 90,
latitude: 135
})
const updateLocation = () => {
location.value = '上海'
}
provide('location', location);
provide('geolocation', geolocation);
return {
updateLocation
}
}
<button @click="updateLocation">改變location</button>
子元件:
import { inject } from 'vue'
export default {
setup() {
const userLocation = inject('location', 'The Universe')
const userGeolocation = inject('geolocation')
return {
userLocation,
userGeolocation
}
}
}
</script>