參考文檔
組合式 API 基礎(chǔ) - Vue3中文文檔
Setup - Vue3中文文檔
一、setup 是什么?
- 為了開(kāi)始使用組合式 API凭涂,我們首先需要一個(gè)可以實(shí)際使用它的地方。在 Vue 組件中屉更,我們將此位置稱為
setup
删壮。 - 它是一個(gè)組件選項(xiàng)。
觀看 Vue Mastery 上的免費(fèi) setup 視頻综慎。
PS: 個(gè)人覺(jué)得這個(gè)視頻講的挺好的莱找。
二酬姆、在哪里使用 setup ?
setup
是一個(gè)組件選項(xiàng)奥溺,所以像別的組件選項(xiàng)一樣辞色,寫(xiě)在組件導(dǎo)出的對(duì)象里。
<script>
export default {
name: "App",
setup() {
// ...
return {
// ...
}
},
}
</script>
三浮定、使用 setup 的正確姿勢(shì)
官方文檔如此描述:
setup
選項(xiàng)應(yīng)該是一個(gè)接受 props
和 context
的函數(shù)相满。
此外,我們從 setup
返回的所有內(nèi)容都將暴露給組件的其余部分 (計(jì)算屬性桦卒、方法立美、生命周期鉤子等等) 以及組件的模板。
個(gè)人覺(jué)得可以理解為:
-
setup
選項(xiàng)應(yīng)該為一個(gè)函數(shù) -
setup
選項(xiàng)函數(shù)接受兩個(gè)參數(shù):props
和context
-
setup
選項(xiàng)函數(shù)需要返回要暴露給組件的內(nèi)容
setup 函數(shù)的參數(shù)
1. setup 函數(shù)中的第一個(gè)參數(shù) —— props
正如在一個(gè)標(biāo)準(zhǔn)組件中所期望的那樣方灾,setup
函數(shù)中的 props
是響應(yīng)式的建蹄,當(dāng)傳入新的 prop 時(shí),它將被更新裕偿。
// MyBook.vue
export default {
props: {
title: String
},
setup(props) {
console.log(props.title)
}
}
WARNING:
但是洞慎,因?yàn)?
props
是響應(yīng)式的,你不能使用 ES6 解構(gòu)嘿棘,因?yàn)樗鼤?huì)消除 prop 的響應(yīng)性劲腿。
如果需要解構(gòu) prop,可以通過(guò)使用setup
函數(shù)中的toRefs
來(lái)安全地完成此操作鸟妙。
// MyBook.vue
import { toRefs } from 'vue'
setup(props) {
const { title } = toRefs(props)
console.log(title.value)
}
2. setup 函數(shù)中的第二個(gè)參數(shù) —— context
context
上下文是一個(gè)普通的 JavaScript 對(duì)象焦人,它暴露三個(gè)組件的 property:
// MyBook.vue
export default {
setup(props, context) {
// Attribute (非響應(yīng)式對(duì)象)
console.log(context.attrs)
// 插槽 (非響應(yīng)式對(duì)象)
console.log(context.slots)
// 觸發(fā)事件 (方法)
console.log(context.emit)
}
}
context
是一個(gè)普通的 JavaScript 對(duì)象,也就是說(shuō)重父,它不是響應(yīng)式的花椭,這意味著你可以安全地對(duì) context
使用 ES6 解構(gòu)。
// MyBook.vue
export default {
setup(props, { attrs, slots, emit }) {
...
}
}
attrs
和 slots
是有狀態(tài)的對(duì)象房午,它們總是會(huì)隨組件本身的更新而更新个从。這意味著你應(yīng)該避免對(duì)它們進(jìn)行解構(gòu),并始終以 attrs.x
或 slots.x
的方式引用 property。請(qǐng)注意嗦锐,與 props
不同,attrs
和 slots
是非響應(yīng)式的沪曙。如果你打算根據(jù) attrs
或 slots
更改應(yīng)用副作用奕污,那么應(yīng)該在 onUpdated
生命周期鉤子中執(zhí)行此操作。
setup 函數(shù)的返回值
1. setup 函數(shù)的返回值 —— 對(duì)象
如果 setup
返回一個(gè)對(duì)象液走,則可以在組件的模板中像傳遞給 setup
的 props
property 一樣訪問(wèn)該對(duì)象的 property:
<!-- MyBook.vue -->
<template>
<!-- 模板中使用會(huì)被自動(dòng)解開(kāi)碳默,所以不需要 .value -->
<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>
注意,從
setup
返回的 refs 在模板中訪問(wèn)時(shí)是被自動(dòng)解開(kāi)的缘眶,因此不應(yīng)在模板中使用.value
嘱根。
2. setup 函數(shù)的返回值 —— 渲染函數(shù)
setup
還可以返回一個(gè)渲染函數(shù),該函數(shù)可以直接使用在同一作用域中聲明的響應(yīng)式狀態(tài):
// MyBook.vue
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])
}
}
新的 setup
組件選項(xiàng)在創(chuàng)建組件之前執(zhí)行巷懈,一旦 props
被解析该抒,并充當(dāng)合成 API 的入口點(diǎn)。
三顶燕、setup 函數(shù)內(nèi)部一般不使用 this
在 setup()
內(nèi)部凑保,this
不會(huì)是該活躍實(shí)例的引用,因?yàn)?setup()
是在解析其它組件選項(xiàng)之前被調(diào)用的涌攻,所以 setup()
內(nèi)部的 this
的行為與其它選項(xiàng)中的 this
完全不同欧引。這在和其它選項(xiàng)式 API 一起使用 setup()
時(shí)可能會(huì)導(dǎo)致混淆。
四恳谎、響應(yīng)式系統(tǒng) API
1. reactive
reactive()
接收一個(gè)普通對(duì)象然后返回該普通對(duì)象的響應(yīng)式代理芝此。等同于 2.x 的 Vue.observable()
const obj = reactive({ count: 0 })
響應(yīng)式轉(zhuǎn)換是“深層的”:會(huì)影響對(duì)象內(nèi)部所有嵌套的屬性∫蛲矗基于 ES2015 的 Proxy 實(shí)現(xiàn)婚苹,返回的代理對(duì)象不等于原始對(duì)象。建議僅使用代理對(duì)象而避免依賴原始對(duì)象婚肆。
<template>
<div id="app">{ state.count }</div>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
// state 現(xiàn)在是一個(gè)響應(yīng)式的狀態(tài)
const state = reactive({
count: 0,
})
}
}
</script>
2. ref
接受一個(gè)參數(shù)值并返回一個(gè)響應(yīng)式且可改變的 ref 對(duì)象租副。ref 對(duì)象擁有一個(gè)指向內(nèi)部值的單一屬性 .value
。
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
如果傳入 ref 的是一個(gè)對(duì)象较性,將調(diào)用 reactive
方法進(jìn)行深層響應(yīng)轉(zhuǎn)換用僧。
-
模板中訪問(wèn)
當(dāng) ref 作為渲染上下文的屬性返回(即在
setup()
返回的對(duì)象中)并在模板中使用時(shí),它會(huì)自動(dòng)解套赞咙,無(wú)需在模板內(nèi)額外書(shū)寫(xiě).value
:<template> <div>{{ count }}</div> </template> <script> export default { setup() { return { count: ref(0), } }, } </script>
-
作為響應(yīng)式對(duì)象的屬性訪問(wèn)
當(dāng) ref 作為 reactive 對(duì)象的 property 被訪問(wèn)或修改時(shí)责循,也將自動(dòng)解套 value 值,其行為類(lèi)似普通屬性:
const count = ref(0) const state = reactive({ count, }) console.log(state.count) // 0 state.count = 1 console.log(count.value) // 1
注意如果將一個(gè)新的 ref 分配給現(xiàn)有的 ref攀操, 將替換舊的 ref:
const otherCount = ref(2) state.count = otherCount console.log(state.count) // 2 console.log(count.value) // 1
注意當(dāng)嵌套在 reactive
Object
中時(shí)院仿,ref 才會(huì)解套。從Array
或者Map
等原生集合類(lèi)中訪問(wèn) ref 時(shí),不會(huì)自動(dòng)解套:const arr = reactive([ref(0)]) // 這里需要 .value console.log(arr[0].value) const map = reactive(new Map([['foo', ref(0)]])) // 這里需要 .value console.log(map.get('foo').value)
-
類(lèi)型定義
interface Ref<T> { value: T } function ref<T>(value: T): Ref<T>
有時(shí)我們可能需要為 ref 做一個(gè)較為復(fù)雜的類(lèi)型標(biāo)注歹垫。我們可以通過(guò)在調(diào)用
ref
時(shí)傳遞泛型參數(shù)來(lái)覆蓋默認(rèn)推導(dǎo):const foo = ref<string | number>('foo') // foo 的類(lèi)型: Ref<string | number> foo.value = 123 // 能夠通過(guò)剥汤!
3. computed
使用響應(yīng)式 computed
API 有兩種方式:
- 傳入一個(gè) getter 函數(shù),返回一個(gè)默認(rèn)不可手動(dòng)修改的 ref 對(duì)象排惨。
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 錯(cuò)誤吭敢!
- 傳入一個(gè)擁有
get
和set
函數(shù)的對(duì)象,創(chuàng)建一個(gè)可手動(dòng)修改的計(jì)算狀態(tài)暮芭。
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
},
})
plusOne.value = 1
console.log(count.value) // 0
-
類(lèi)型定義
// 只讀的 function computed<T>(getter: () => T): Readonly<Ref<Readonly<T>>> // 可更改的 function computed<T>(options: { get: () => T set: (value: T) => void }): Ref<T>
4. readonly
傳入一個(gè)對(duì)象(響應(yīng)式或普通)或 ref鹿驼,返回一個(gè)原始對(duì)象的只讀代理。一個(gè)只讀的代理是“深層的”辕宏,對(duì)象內(nèi)部任何嵌套的屬性也都是只讀的畜晰。
const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
// 依賴追蹤
console.log(copy.count)
})
// original 上的修改會(huì)觸發(fā) copy 上的偵聽(tīng)
original.count++
// 無(wú)法修改 copy 并會(huì)被警告
copy.count++ // warning!
5. watchEffect
立即執(zhí)行傳入的一個(gè)函數(shù),并響應(yīng)式追蹤其依賴瑞筐,并在其依賴變更時(shí)重新運(yùn)行該函數(shù)凄鼻。
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> 打印出 0
setTimeout(() => {
count.value++
// -> 打印出 1
}, 100)
- 停止偵聽(tīng)
當(dāng) watchEffect
在組件的 setup()
函數(shù)或生命周期鉤子被調(diào)用時(shí), 偵聽(tīng)器會(huì)被鏈接到該組件的生命周期面哼,并在組件卸載時(shí)自動(dòng)停止野宜。
在一些情況下,也可以顯式調(diào)用返回值以停止偵聽(tīng):
const stop = watchEffect(() => {
/* ... */
})
// 之后
stop()
- 清除副作用
有時(shí)副作用函數(shù)會(huì)執(zhí)行一些異步的副作用, 這些響應(yīng)需要在其失效時(shí)清除(即完成之前狀態(tài)已改變了)魔策。所以偵聽(tīng)副作用傳入的函數(shù)可以接收一個(gè) onInvalidate
函數(shù)作入?yún)? 用來(lái)注冊(cè)清理失效時(shí)的回調(diào)匈子。當(dāng)以下情況發(fā)生時(shí),這個(gè)失效回調(diào)會(huì)被觸發(fā):
副作用即將重新執(zhí)行時(shí)
偵聽(tīng)器被停止 (如果在
setup()
或 生命周期鉤子函數(shù)中使用了watchEffect
, 則在卸載組件時(shí))
watchEffect((onInvalidate) => {
const token = performAsyncOperation(id.value)
onInvalidate(() => {
// id 改變時(shí) 或 停止偵聽(tīng)時(shí)
// 取消之前的異步操作
token.cancel()
})
})
我們之所以是通過(guò)傳入一個(gè)函數(shù)去注冊(cè)失效回調(diào)闯袒,而不是從回調(diào)返回它(如 React useEffect
中的方式)虎敦,是因?yàn)榉祷刂祵?duì)于異步錯(cuò)誤處理很重要。
在執(zhí)行數(shù)據(jù)請(qǐng)求時(shí)政敢,副作用函數(shù)往往是一個(gè)異步函數(shù):
const data = ref(null)
watchEffect(async () => {
data.value = await fetchData(props.id)
})
我們知道異步函數(shù)都會(huì)隱式地返回一個(gè) Promise其徙,但是清理函數(shù)必須要在 Promise 被 resolve 之前被注冊(cè)。另外喷户,Vue 依賴這個(gè)返回的 Promise 來(lái)自動(dòng)處理 Promise 鏈上的潛在錯(cuò)誤唾那。
- 副作用刷新時(shí)機(jī)
Vue 的響應(yīng)式系統(tǒng)會(huì)緩存副作用函數(shù),并異步地刷新它們褪尝,這樣可以避免同一個(gè) tick 中多個(gè)狀態(tài)改變導(dǎo)致的不必要的重復(fù)調(diào)用闹获。在核心的具體實(shí)現(xiàn)中, 組件的更新函數(shù)也是一個(gè)被偵聽(tīng)的副作用。當(dāng)一個(gè)用戶定義的副作用函數(shù)進(jìn)入隊(duì)列時(shí), 會(huì)在所有的組件更新后執(zhí)行:
<template>
<div>{{ count }}</div>
</template>
<script>
export default {
setup() {
const count = ref(0)
watchEffect(() => {
console.log(count.value)
})
return {
count,
}
},
}
</script>
在這個(gè)例子中:
-
count
會(huì)在初始運(yùn)行時(shí)同步打印出來(lái) - 更改
count
時(shí)河哑,將在組件更新后執(zhí)行副作用避诽。
請(qǐng)注意,初始化運(yùn)行是在組件 mounted
之前執(zhí)行的璃谨。因此沙庐,如果你希望在編寫(xiě)副作用函數(shù)時(shí)訪問(wèn) DOM(或模板 ref)鲤妥,請(qǐng)?jiān)?onMounted
鉤子中進(jìn)行:
onMounted(() => {
watchEffect(() => {
// 在這里可以訪問(wèn)到 DOM 或者 template refs
})
})
如果副作用需要同步或在組件更新之前重新運(yùn)行,我們可以傳遞一個(gè)擁有 flush
屬性的對(duì)象作為選項(xiàng)(默認(rèn)為 'post'
):
// 同步運(yùn)行
watchEffect(
() => {
/* ... */
},
{
flush: 'sync',
}
)
// 組件更新前執(zhí)行
watchEffect(
() => {
/* ... */
},
{
flush: 'pre',
}
)
- 偵聽(tīng)器調(diào)試
onTrack
和 onTrigger
選項(xiàng)可用于調(diào)試一個(gè)偵聽(tīng)器的行為拱雏。
當(dāng)一個(gè) reactive 對(duì)象屬性或一個(gè) ref 作為依賴被追蹤時(shí)棉安,將調(diào)用
onTrack
依賴項(xiàng)變更導(dǎo)致副作用被觸發(fā)時(shí),將調(diào)用
onTrigger
這兩個(gè)回調(diào)都將接收到一個(gè)包含有關(guān)所依賴項(xiàng)信息的調(diào)試器事件古涧。建議在以下回調(diào)中編寫(xiě) debugger
語(yǔ)句來(lái)檢查依賴關(guān)系:
watchEffect(
() => {
/* 副作用的內(nèi)容 */
},
{
onTrigger(e) {
debugger
},
}
)
onTrack
和 onTrigger
僅在開(kāi)發(fā)模式下生效垂券。
-
類(lèi)型定義
function watchEffect( effect: (onInvalidate: InvalidateCbRegistrator) => void, options?: WatchEffectOptions ): StopHandle interface WatchEffectOptions { flush?: 'pre' | 'post' | 'sync' onTrack?: (event: DebuggerEvent) => void onTrigger?: (event: DebuggerEvent) => void } interface DebuggerEvent { effect: ReactiveEffect target: any type: OperationTypes key: string | symbol | undefined } type InvalidateCbRegistrator = (invalidate: () => void) => void type StopHandle = () => void
6. watch
watch
API 完全等效于 2.x this.$watch
(以及 watch
中相應(yīng)的選項(xiàng))。watch
需要偵聽(tīng)特定的數(shù)據(jù)源羡滑,并在回調(diào)函數(shù)中執(zhí)行副作用。默認(rèn)情況是懶執(zhí)行的算芯,也就是說(shuō)僅在偵聽(tīng)的源變更時(shí)才執(zhí)行回調(diào)柒昏。
-
對(duì)比
watchEffect
,watch
允許我們:- 懶執(zhí)行副作用熙揍;
- 更明確哪些狀態(tài)的改變會(huì)觸發(fā)偵聽(tīng)器重新運(yùn)行副作用职祷;
- 訪問(wèn)偵聽(tīng)狀態(tài)變化前后的值。
-
偵聽(tīng)單個(gè)數(shù)據(jù)源
偵聽(tīng)器的數(shù)據(jù)源可以是一個(gè)擁有返回值的 getter 函數(shù)届囚,也可以是 ref:
// 偵聽(tīng)一個(gè) getter const state = reactive({ count: 0 }) watch( () => state.count, (count, prevCount) => { /* ... */ } ) // 直接偵聽(tīng)一個(gè) ref const count = ref(0) watch(count, (count, prevCount) => { /* ... */ })
-
偵聽(tīng)多個(gè)數(shù)據(jù)源
watcher
也可以使用數(shù)組來(lái)同時(shí)偵聽(tīng)多個(gè)源:watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => { /* ... */ })
-
與
watchEffect
共享的行為watch 和 watchEffect 在停止偵聽(tīng), 清除副作用 (相應(yīng)地
onInvalidate
會(huì)作為回調(diào)的第三個(gè)參數(shù)傳入)有梆,副作用刷新時(shí)機(jī) 和 偵聽(tīng)器調(diào)試 等方面行為一致. -
類(lèi)型定義
// 偵聽(tīng)單數(shù)據(jù)源 function watch<T>( source: WatcherSource<T>, callback: ( value: T, oldValue: T, onInvalidate: InvalidateCbRegistrator ) => void, options?: WatchOptions ): StopHandle // 偵聽(tīng)多數(shù)據(jù)源 function watch<T extends WatcherSource<unknown>[]>( sources: T callback: ( values: MapSources<T>, oldValues: MapSources<T>, onInvalidate: InvalidateCbRegistrator ) => void, options? : WatchOptions ): StopHandle type WatcherSource<T> = Ref<T> | (() => T) type MapSources<T> = { [K in keyof T]: T[K] extends WatcherSource<infer V> ? V : never } // 共有的屬性 請(qǐng)查看 `watchEffect` 的類(lèi)型定義 interface WatchOptions extends WatchEffectOptions { immediate?: boolean // default: false deep?: boolean }
#生命周期鉤子函數(shù)
可以直接導(dǎo)入 onXXX
一族的函數(shù)來(lái)注冊(cè)生命周期鉤子:
未完待續(xù)。意系。泥耀。