原文地址 v3.cn.vuejs.org
# 什么是組合式 API?
如果我們能夠將與同一個邏輯關注點相關的代碼配置在一起,這樣會更好。而這正是組合式 API 使我們能夠做到的。
# 組合式 API 基礎
# setup
組件選項
新的 setup
組件選項在創建組件之前執行,一旦 props
被解析,就作為組合式 API 的入口點。
WARNING
由於在執行 setup
時,組件實例尚未被創建,因此在 setup
選項中沒有 this
。這意味着,除了 props
之外,你將無法訪問組件中聲明的任何屬性——本地狀態、計算屬性或方法。
setup
選項應該是一個接受 props
和 context
的函數,我們將在稍后討論。
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
setup(props) {
console.log(props)
return {} // 這里返回的任何內容都可以用於組件的其余部分
}
}
# 帶 ref
的響應式變量
在 Vue 3.0 中,我們可以通過一個新的 ref
函數使任何響應式變量在任何地方起作用,如下所示:
import { ref } from 'vue'
const counter = ref(0)
1
2
3
ref
接受參數,並將其包裹在一個帶有 value
property 的對象中返回,然后可以使用該 property 訪問或更改響應式變量的值:
import { ref } from 'vue'
const counter = ref(0)
console.log(counter)
console.log(counter.value)
counter.value++
console.log(counter.value)
1
2
3
4
5
6
7
8
9
將值封裝在一個對象中,看似沒有必要,但為了保持 JavaScript 中不同數據類型的行為統一,這是必須的。這是因為在 JavaScript 中,Number
或 String
等基本類型是通過值傳遞的,而不是通過引用傳遞的:
在任何值周圍都有一個封裝對象,這樣我們就可以在整個應用中安全地傳遞它,而不必擔心在某個地方失去它的響應性。
提示
換句話說,ref
為我們的值創建了一個響應式引用。在整個組合式 API 中會經常使用引用的概念。
回到我們的例子,讓我們創建一個響應式的 repositories
變量:
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from 'vue'
setup (props) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user)
}
return {
repositories,
getUserRepositories
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
完成!現在,每當我們調用 getUserRepositories
時,repositories
都將發生變化,視圖也會更新以反映變化。我們的組件現在應該如下所示:
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from 'vue'
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
setup (props) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user)
}
return {
repositories,
getUserRepositories
}
},
data () {
return {
filters: { ... },
searchQuery: ''
}
},
computed: {
filteredRepositories () { ... },
repositoriesMatchingSearchQuery () { ... },
},
watch: {
user: 'getUserRepositories'
},
methods: {
updateFilters () { ... },
},
mounted () {
this.getUserRepositories()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
我們已經將第一個邏輯關注點中的幾個部分移到了 setup
方法中,它們彼此非常接近。剩下的就是在 mounted
鈎子中調用 getUserRepositories
,並設置一個監聽器,以便在 user
prop 發生變化時執行此操作。
我們將從生命周期鈎子開始。
# 生命周期鈎子注冊內部 setup
為了使組合式 API 的功能比選項式 API 更加完整,我們還需要一種在 setup
中注冊生命周期鈎子的方法。這要歸功於從 Vue 導出的幾個新函數。組合式 API 上的生命周期鈎子與選項式 API 的名稱相同,但前綴為 on
:即 mounted
會看起來像 onMounted
。
這些函數接受一個回調,當鈎子被組件調用時,該回調將被執行。
讓我們將其添加到 setup
函數中:
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted } from 'vue'
setup (props) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user)
}
onMounted(getUserRepositories)
return {
repositories,
getUserRepositories
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
現在我們需要對 user
prop 的變化做出反應。為此,我們將使用獨立的 watch
函數。
# watch
響應式更改
就像我們在組件中使用 watch
選項在 user
property 上設置偵聽器一樣,我們也可以使用從 Vue 導入的 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)
})
1
2
3
4
5
6
每當 counter
被修改時,例如 counter.value=5
,偵聽將觸發並執行回調 (第二個參數),在本例中,它將把 'The new counter value is:5'
記錄到我們的控制台中。
以下是等效的選項式 API:
export default {
data() {
return {
counter: 0
}
},
watch: {
counter(newValue, oldValue) {
console.log('The new counter value is: ' + this.counter)
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
有關 watch
的詳細信息,請參閱我們的深入指南。
現在我們將其應用到我們的示例中:
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs } from 'vue'
setup (props) {
const { user } = toRefs(props)
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(user.value)
}
onMounted(getUserRepositories)
watch(user, getUserRepositories)
return {
repositories,
getUserRepositories
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
你可能已經注意到在我們的 setup
的頂部使用了 toRefs
。這是為了確保我們的偵聽器能夠對 user
prop 所做的變化做出反應。
注釋:可能是因為 props.user 是字符串,不是引用類型所以結構后就不再響應。如果被結構的目標是引用類型應該會保持響應?
有了這些變化,我們就把第一個邏輯關注點移到了一個地方。我們現在可以對第二個關注點執行相同的操作——基於 searchQuery
進行過濾,這次是使用計算屬性。
# 獨立的 computed
屬性
與 ref
和 watch
類似,也可以使用從 Vue 導入的 computed
函數在 Vue 組件外部創建計算屬性。讓我們回到我們的 counter 例子:
import { ref, computed } from 'vue'
const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)
counter.value++
console.log(counter.value)
console.log(twiceTheCounter.value)
1
2
3
4
5
6
7
8
在這里,computed
函數返回一個作為 computed
的第一個參數傳遞的 getter 類回調的輸出的一個_只讀_的響應式引用。為了訪問新創建的計算變量的 value,我們需要像使用 ref
一樣使用 .value
property。
讓我們將搜索功能移到 setup
中:
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs, computed } from 'vue'
setup (props) {
const { user } = toRefs(props)
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(user.value)
}
onMounted(getUserRepositories)
watch(user, getUserRepositories)
const searchQuery = ref('')
const repositoriesMatchingSearchQuery = computed(() => {
return repositories.value.filter(
repository => repository.name.includes(searchQuery.value)
)
})
return {
repositories,
getUserRepositories,
searchQuery,
repositoriesMatchingSearchQuery
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
對於其他的邏輯關注點我們也可以這樣做,但是你可能已經在問這個問題了——這不就是把代碼移到 setup
選項並使它變得非常大嗎?嗯,那是真的。這就是為什么在繼續其他任務之前,我們將首先將上述代碼提取到一個獨立的組合式函數。讓我們從創建 useUserRepositories
開始:
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
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
然后是搜索功能:
import { ref, computed } from 'vue'
export default function useRepositoryNameSearch(repositories) {
const searchQuery = ref('')
const repositoriesMatchingSearchQuery = computed(() => {
return repositories.value.filter(repository => {
return repository.name.includes(searchQuery.value)
})
})
return {
searchQuery,
repositoriesMatchingSearchQuery
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
現在在單獨的文件中有了這兩個功能,我們就可以開始在組件中使用它們了。以下是如何做到這一點:
import useUserRepositories from '@/composables/useUserRepositories'
import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
import { toRefs } from 'vue'
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
setup (props) {
const { user } = toRefs(props)
const { repositories, getUserRepositories } = useUserRepositories(user)
const {
searchQuery,
repositoriesMatchingSearchQuery
} = useRepositoryNameSearch(repositories)
return {
repositories: repositoriesMatchingSearchQuery,
getUserRepositories,
searchQuery,
}
},
data () {
return {
filters: { ... },
}
},
computed: {
filteredRepositories () { ... },
},
methods: {
updateFilters () { ... },
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
此時,你可能已經知道了其中的奧妙,所以讓我們跳到最后,遷移剩余的過濾功能。我們不需要深入了解實現細節,因為這不是本指南的重點。
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,
required: true
}
},
setup(props) {
const { user } = toRefs(props)
const { repositories, getUserRepositories } = useUserRepositories(user)
const {
searchQuery,
repositoriesMatchingSearchQuery
} = useRepositoryNameSearch(repositories)
const {
filters,
updateFilters,
filteredRepositories
} = useRepositoryFilters(repositoriesMatchingSearchQuery)
return {
repositories: filteredRepositories,
getUserRepositories,
searchQuery,
filters,
updateFilters
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
我們完成了!
請記住,我們只觸及了組合式 API 的表面以及它允許我們做什么。要了解更多信息,請參閱深入指南。