下面 先從setup Fuction 開始編寫Compositon API.
1⃣️ setup
新的 setup
組件選項 在 創建組件之前執行,一旦 props
被解析,並充當合成 API 的入口點。
我們從 setup
返回的所有內容 都將暴露給組件的其餘部分 (計算屬性、方法、生命週期鉤子等等) 以及組件的模板。
⚠️ setup執行時間:The setup
executes before any of the following options are evaluated:( Components、 Props 、Data、Methods、Computed Properties、Lifecycle methods)
⚠️ 由於在執行 setup
時,尚未創建組件實例,因此在 setup
選項中沒有 this
。這意味着,除了 props
之外,你將無法訪問組件中聲明的任何屬性——本地狀態、計算屬性或方法。與其他Component選項不同,setup該方法無權訪問“ this”。爲了獲得對屬性的訪問權限(之前用this獲取的),setup
具有兩個可選參數。
a、參數
第一個是props
,例如:
// src/components/UserRepositories.vue export default { components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList }, props: { user: { type: String } }, setup(props) { console.log(props) // { user: '' } return {} // 這裏返回的任何內容都可以用於組件的其餘部分 } // 組件的“其餘部分” }
⚠️ setup
函數中的 props
是響應式的,當傳入新的 prop 時,它將被更新。
但是,因爲 props
是響應式的,你不能使用 ES6 解構,因爲它會消除 prop 的響應性。
如果需要解構 prop,可以通過使用 setup
函數中的 toRefs
來安全地完成此操作。
// MyBook.vue import { toRefs } from 'vue' setup(props) { const { title } = toRefs(props) console.log(title.value) }
第二個參數是context
,它可以訪問一堆有用的數據:
// MyBook.vue export default { setup(props, context) { // Attribute (非響應式對象) console.log(context.attrs) // 插槽 (非響應式對象) console.log(context.slots) // 觸發事件 (方法) console.log(context.emit)
context.parent;
context.root;
}
}
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
生命週期鉤子中執行此操作 ... } }
b、訪問組件的property
執行 setup
時,組件實例尚未被創建。因此,你只能訪問以下 property:props
、attrs、
slots、
emit
換句話說,你將無法訪問以下組件選項:data 、
computed、
methods
c、結合模板使用
如果 setup
返回一個對象,則可以在組件的模板中 像傳遞給 setup
的 props
property 一樣 訪問該對象的 property:
⚠️ 注意,從 setup
返回的 refs 在模板中訪問時是被自動解開的,因此不應在模板中使用 .value
。
<template> <div>{{ readersNumber }} {{ book.title }}</div> </template> <script> import { ref, reactive } from 'vue' export default { setup() { const readersNumber = ref(0) const book = reactive({ title: 'Vue 3 Guide' }) // expose to template return { readersNumber, book } } } </script>
d、使用渲染函數
setup
還可以返回一個渲染函數,該函數可以直接使用 在同一作用域中聲明的響應式狀態:
import { h, ref, reactive } from 'vue' export default { setup() { const readersNumber = ref(0) const book = reactive({ title: 'Vue 3 Guide' }) // Please note that we need to explicitly expose ref value here return () => h('div', [readersNumber.value, book.title]) } }
e、使用this
在 setup()
內部,this
不會是該活躍實例的引用
因爲 setup()
是在解析其它組件選項之前被調用的,所以 setup()
內部的 this
的行爲與其它選項中的 this
完全不同。
這在和其它選項式 API 一起使用 setup()
時可能會導致混淆。
2⃣️、ref函數 & 模版引用
a、ref函數
<template> <div>Capacity: {{ capacity }}</div> </template> <script> import { ref } from "vue"; export default { setup() { const capacity = ref(3); // 基本上,它將原始整數(3)包裝在一個對象中,這將使我們能夠跟蹤更改。以前data()選項 已經將我們的原始(容量)包裝在一個對象內。 return { capacity }; //最後一步,我們需要顯式返回一個對象,該對象的屬性需要模板正確呈現。這個返回的對象是我們如何在中公開需要訪問哪些數據的方法renderContext。像這樣明確表示有點冗長,但這也是故意的。它有助於長期維護,因爲我們可以控制暴露給模板的內容,並跟蹤定義模板屬性的位置。 } }; </script>
b、模版引用
作爲模板使用的 ref 的行爲與任何其他 ref 一樣:它們是 響應式的,可以傳遞到 (或從中返回) 複合函數中。
爲了獲得 對模板內元素或組件實例的引用,我們可以像往常一樣聲明 ref 並從 setup() 返回。
<template> <div ref="root">This is a root element</div> </template> <script> import { ref, onMounted } from 'vue' export default { setup() { const root = ref(null) onMounted(() => { // DOM元素將在初始渲染後分配給ref console.log(root.value) // <div>這是根元素</div> }) return { root } } } </script>
v-for
中的用法具體地址:https://vue3js.cn/docs/zh/guide/composition-api-template-refs.html
3⃣️、provide/inject
通常父傳遞給子組件,可用 props。 但是當有深層結構的傳遞,props就滿足不了要求,可以讓父組件provide, 子(孫子)組件inject.
可以看成“ long range props”。
位置:兩者都只能在當前活動實例的 setup()
期間調用。
vue2中👇
app.component('todo-list', { data() { return { todos: ['Feed a cat', 'Buy tickets'] } }, provide() {
//情況1 return { todoLength: this.todos.length //⚠️ 要訪問 組件實例 property,我們需要將provide
轉換爲 返回對象的函數。 }
//情況2
return {
todoLength: Vue.computed(() => this.todos.length) //如果我們想 對祖先組件中的更改 做出反應。
}
},
template: `
...
`
})
vue3中👇
<!-- src/components/MyMap.vue --> <template> <MyMarker /> </template> <script> import { provide, reactive, readonly, ref } from 'vue' import MyMarker from './MyMarker.vue export default { components: { MyMarker }, setup() { const location = ref('North Pole') //爲了使 依賴值和注入值 產生 響應性,在提供值時要使用 reactive,ref 函數. const geolocation = reactive({ longitude: 90, latitude: 135 }) const updateLocation = () => { location.value = 'South Pole' //有時我們需要在 注入數據的組件內部更新注入的數據。在這種情況下,我們建議 提供一個方法來負責改變響應式 property } provide('location', readonly(location)) // provide倆參數,第一個是name(必填),第二個是默認值(可選)。 provide('geolocation', readonly(geolocation)) // 確保通過provide 傳遞的數據不會被 注入的組件更改, provide('updateLocation', updateLocation) } } </script>
//使用注入 <script> import { inject } from 'vue' export default { setup() { const userLocation = inject('location', 'The Universe') //顯式導入,inject函數倆參數:要注入的property的名稱(必填);一個默認的值(可選) const userGeolocation = inject('geolocation')const updateUserLocation = inject('updateLocation')
return {
userLocation, userGeolocation,updateUserLocation
}
}
}
</script>
文檔地址:https://vue3js.cn/docs/zh/guide/component-provide-inject.html
3⃣️、setup中的鉤子
組合式 API 上的生命週期鉤子與選項式 API 的名稱相同,但前綴爲 on
:即 mounted
看起來像 onMounted
。
4⃣️、watch
響應式更改
它接受 3 個參數:
- 一個響應式引用 或 我們想要偵聽的 getter 函數
- 一個回調
- 可選的配置選項
import { ref, watch } from 'vue'
const counter = ref(0)
watch(counter, (newValue, oldValue) => {
console.log('The new counter value is: ' + counter.value)
})
//每當 counter 被修改時 counter.value=5,watch 將觸發並執行回調 (第二個參數),在本例中,它將把 'The new counter value is:5' 記錄到我們的控制檯中
5⃣️、toRefs
toRefs
是 爲了確保 偵聽器能夠對 user
prop 所做的更改 做出反應。
toRefs
是 爲了確保 偵聽器能夠對 user
prop 所做的更改 做出反應。
6⃣️、獨立的 computed
屬性
與 ref
和 watch
類似,也可以使用 從 Vue 導入的 computed
函數,在 Vue 組件外部創建計算屬性。
computed
函數返回一個作爲 computed
的第一個參數傳遞的 getter 類回調的輸出的一個只讀的響應式引用。
爲了訪問新創建的計算變量的 value,我們需要像使用 ref
一樣使用 .value
property。
import { ref, computed } from 'vue'
const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)
counter.value++
console.log(counter.value) // 1
console.log(twiceTheCounter.value) // 2
7⃣️、組合式函數
(現在只有兩個功能,獲取用戶數據➕搜索功能。對於其他的邏輯關注點我們也可以這樣做,setup隨着功能的增多,會越來越大👇)
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs, computed } from 'vue'
// in our component
setup (props) {
// 使用 `toRefs` 創建對 props 的 `user` property 的響應式引用
const { user } = toRefs(props)
const repositories = ref([])
const getUserRepositories = async () => {
// 更新 `props.user ` 到 `user.value` 訪問引用值
repositories.value = await fetchUserRepositories(user.value)
}
onMounted(getUserRepositories)
// 在用戶 prop 的響應式引用上設置一個偵聽器
watch(user, getUserRepositories)
const searchQuery = ref('')
const repositoriesMatchingSearchQuery = computed(() => {
return repositories.value.filter(
repository => repository.name.includes(searchQuery.value)
)
})
return {
repositories,
getUserRepositories,
searchQuery,
repositoriesMatchingSearchQuery
}
}
所以要先將一個功能邏輯的代碼提取到一個獨立的組合式函數(單獨的文件),然後再調用👇。
// 例 :🌰 創建一個獨立的組合式函數-> useUserRepositories
// src/composables/useUserRepositories.js
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch } from 'vue'
export default function useUserRepositories(user) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(user.value)
}
onMounted(getUserRepositories)
watch(user, getUserRepositories)
return {
repositories,
getUserRepositories
}
}
如何使用該函數 👇
// src/components/UserRepositories.vue import { toRefs } from 'vue' import useUserRepositories from '@/composables/useUserRepositories' import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch' import useRepositoryFilters from '@/composables/useRepositoryFilters'
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: { user: { type: String } },
setup(props) {
const { user } = toRefs(props)
const { repositories, getUserRepositories } = useUserRepositories(user)
const { searchQuery,repositoriesMatchingSearchQuery } = useRepositoryNameSearch(repositories)
const { filters, updateFilters, filteredRepositories } = useRepositoryFilters(repositoriesMatchingSearchQuery)
return {
// 因爲我們並不關心未經過濾的倉庫
// 我們可以在 `repositories` 名稱下暴露過濾後的結果
repositories: filteredRepositories,
getUserRepositories,
searchQuery,
filters,
updateFilters
}
}
}
總結: