Vue3 API 匯總手冊
setup
setup
函數(shù)是一個新的組件選項远豺。作為在組件內(nèi)使用 Composition API 的入口點窑滞。
-
調(diào)用時機
創(chuàng)建組件實例良漱,然后初始化
props
毅弧,緊接著就調(diào)用setup
函數(shù)。從生命周期鉤子的視角來看喜爷,它會在beforeCreate
鉤子之前被調(diào)用 -
模板中使用
如果
setup
返回一個對象冗疮,則對象的屬性將會被合并到組件模板的渲染上下文:<template> <div>{{ count }} {{ object.foo }}</div> </template> <script> import { ref, reactive } from 'vue' export default { setup() { const count = ref(0) const object = reactive({ foo: 'bar' }) // 暴露給模板 return { count, object, } }, } </script>
注意
setup
返回的 ref 在模板中會自動解開,不需要寫.value
贞奋。 -
渲染函數(shù) / JSX 中使用
setup
也可以返回一個函數(shù)赌厅,函數(shù)中也能使用當(dāng)前setup
函數(shù)作用域中的響應(yīng)式數(shù)據(jù):import { h, ref, reactive } from 'vue' export default { setup() { const count = ref(0) const object = reactive({ foo: 'bar' }) return () => h('div', [count.value, object.foo]) }, }
-
參數(shù)
該函數(shù)接收
props
作為其第一個參數(shù):export default { props: { name: String, }, setup(props) { console.log(props.name) }, }
注意
props
對象是響應(yīng)式的,watchEffect
或watch
會觀察和響應(yīng)props
的更新:export default { props: { name: String, }, setup(props) { watchEffect(() => { console.log(`name is: ` + props.name) }) }, }
然而不要解構(gòu)
props
對象轿塔,那樣會使其失去響應(yīng)性:export default { props: { name: String, }, setup({ name }) { watchEffect(() => { console.log(`name is: ` + name) // Will not be reactive! }) }, }
在開發(fā)過程中特愿,
props
對象對用戶空間代碼是不可變的(用戶代碼嘗試修改props
時會觸發(fā)警告)仲墨。第二個參數(shù)提供了一個上下文對象,從原來 2.x 中
this
選擇性地暴露了一些 property揍障。const MyComponent = { setup(props, context) { context.attrs context.slots context.emit }, }
attrs
和slots
都是內(nèi)部組件實例上對應(yīng)項的代理目养,可以確保在更新后仍然是最新值。所以可以解構(gòu)毒嫡,無需擔(dān)心后面訪問到過期的值:const MyComponent = { setup(props, { attrs }) { // 一個可能之后回調(diào)用的簽名 function onClick() { console.log(attrs.foo) // 一定是最新的引用癌蚁,沒有丟失響應(yīng)性 } }, }
出于一些原因?qū)?
props
作為第一個參數(shù),而不是包含在上下文中:- 組件使用
props
的場景更多兜畸,有時候甚至只使用props
- 將
props
獨立出來作為第一個參數(shù)努释,可以讓 TypeScript 對props
單獨做類型推導(dǎo),不會和上下文中的其他屬性相混淆咬摇。這也使得setup
伐蒂、render
和其他使用了 TSX 的函數(shù)式組件的簽名保持一致。
- 組件使用
-
this
的用法this
在setup()
中不可用肛鹏。由于setup()
在解析 2.x 選項前被調(diào)用逸邦,setup()
中的this
將與 2.x 選項中的this
完全不同。同時在setup()
和 2.x 選項中使用this
時將造成混亂在扰。在setup()
中避免這種情況的另一個原因是:這對于初學(xué)者來說缕减,混淆這兩種情況的this
是非常常見的錯誤:setup() { function onClick() { this // 這里 `this` 與你期望的不一樣! } }
-
類型定義
interface Data { [key: string]: unknown } interface SetupContext { attrs: Data slots: Slots emit: (event: string, ...args: unknown[]) => void } function setup(props: Data, context: SetupContext): Data
提示
為了獲得傳遞給
setup()
參數(shù)的類型推斷芒珠,需要使用defineComponent
桥狡。
#響應(yīng)式系統(tǒng) API
#reactive
接收一個普通對象然后返回該普通對象的響應(yīng)式代理。等同于 2.x 的 Vue.observable()
const obj = reactive({ count: 0 })
響應(yīng)式轉(zhuǎn)換是“深層的”:會影響對象內(nèi)部所有嵌套的屬性妓局∽芊牛基于 ES2015 的 Proxy 實現(xiàn)呈宇,返回的代理對象不等于原始對象好爬。建議僅使用代理對象而避免依賴原始對象。
-
類型定義
function reactive<T extends object>(raw: T): T
#ref
接受一個參數(shù)值并返回一個響應(yīng)式且可改變的 ref 對象甥啄。ref 對象擁有一個指向內(nèi)部值的單一屬性 .value
存炮。
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
如果傳入 ref 的是一個對象,將調(diào)用 reactive
方法進行深層響應(yīng)轉(zhuǎn)換蜈漓。
-
模板中訪問
當(dāng) ref 作為渲染上下文的屬性返回(即在
setup()
返回的對象中)并在模板中使用時穆桂,它會自動解套,無需在模板內(nèi)額外書寫.value
:<template> <div>{{ count }}</div> </template> <script> export default { setup() { return { count: ref(0), } }, } </script>
-
作為響應(yīng)式對象的屬性訪問
當(dāng) ref 作為 reactive 對象的 property 被訪問或修改時融虽,也將自動解套 value 值享完,其行為類似普通屬性:
const count = ref(0) const state = reactive({ count, }) console.log(state.count) // 0 state.count = 1 console.log(count.value) // 1
注意如果將一個新的 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
中時有额,ref 才會解套般又。從Array
或者Map
等原生集合類中訪問 ref 時彼绷,不會自動解套: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)
-
類型定義
interface Ref<T> { value: T } function ref<T>(value: T): Ref<T>
有時我們可能需要為 ref 做一個較為復(fù)雜的類型標(biāo)注。我們可以通過在調(diào)用
ref
時傳遞泛型參數(shù)來覆蓋默認推導(dǎo):const foo = ref<string | number>('foo') // foo 的類型: Ref<string | number> foo.value = 123 // 能夠通過茴迁!
#computed
傳入一個 getter 函數(shù),返回一個默認不可手動修改的 ref 對象。
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 錯誤抚官!
或者傳入一個擁有 get
和 set
函數(shù)的對象桂对,創(chuàng)建一個可手動修改的計算狀態(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
-
類型定義
// 只讀的 function computed<T>(getter: () => T): Readonly<Ref<Readonly<T>>> // 可更改的 function computed<T>(options: { get: () => T set: (value: T) => void }): Ref<T>
#readonly
傳入一個對象(響應(yīng)式或普通)或 ref倦卖,返回一個原始對象的只讀代理洒擦。一個只讀的代理是“深層的”,對象內(nèi)部任何嵌套的屬性也都是只讀的怕膛。
const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
// 依賴追蹤
console.log(copy.count)
})
// original 上的修改會觸發(fā) copy 上的偵聽
original.count++
// 無法修改 copy 并會被警告
copy.count++ // warning!
#watchEffect
立即執(zhí)行傳入的一個函數(shù)秘遏,并響應(yīng)式追蹤其依賴,并在其依賴變更時重新運行該函數(shù)嘉竟。
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> 打印出 0
setTimeout(() => {
count.value++
// -> 打印出 1
}, 100)
#停止偵聽
當(dāng) watchEffect
在組件的 setup()
函數(shù)或生命周期鉤子被調(diào)用時邦危, 偵聽器會被鏈接到該組件的生命周期,并在組件卸載時自動停止舍扰。
在一些情況下倦蚪,也可以顯式調(diào)用返回值以停止偵聽:
const stop = watchEffect(() => {
/* ... */
})
// 之后
stop()
#清除副作用
有時副作用函數(shù)會執(zhí)行一些異步的副作用, 這些響應(yīng)需要在其失效時清除(即完成之前狀態(tài)已改變了)。所以偵聽副作用傳入的函數(shù)可以接收一個 onInvalidate
函數(shù)作入?yún)? 用來注冊清理失效時的回調(diào)边苹。當(dāng)以下情況發(fā)生時陵且,這個失效回調(diào)會被觸發(fā):
- 副作用即將重新執(zhí)行時
- 偵聽器被停止 (如果在
setup()
或 生命周期鉤子函數(shù)中使用了watchEffect
, 則在卸載組件時)
watchEffect((onInvalidate) => {
const token = performAsyncOperation(id.value)
onInvalidate(() => {
// id 改變時 或 停止偵聽時
// 取消之前的異步操作
token.cancel()
})
})
我們之所以是通過傳入一個函數(shù)去注冊失效回調(diào),而不是從回調(diào)返回它(如 React useEffect
中的方式)个束,是因為返回值對于異步錯誤處理很重要慕购。
在執(zhí)行數(shù)據(jù)請求時,副作用函數(shù)往往是一個異步函數(shù):
const data = ref(null)
watchEffect(async () => {
data.value = await fetchData(props.id)
})
我們知道異步函數(shù)都會隱式地返回一個 Promise茬底,但是清理函數(shù)必須要在 Promise 被 resolve 之前被注冊沪悲。另外,Vue 依賴這個返回的 Promise 來自動處理 Promise 鏈上的潛在錯誤阱表。
#副作用刷新時機
Vue 的響應(yīng)式系統(tǒng)會緩存副作用函數(shù)殿如,并異步地刷新它們,這樣可以避免同一個 tick 中多個狀態(tài)改變導(dǎo)致的不必要的重復(fù)調(diào)用最爬。在核心的具體實現(xiàn)中, 組件的更新函數(shù)也是一個被偵聽的副作用涉馁。當(dāng)一個用戶定義的副作用函數(shù)進入隊列時, 會在所有的組件更新后執(zhí)行:
<template>
<div>{{ count }}</div>
</template>
<script>
export default {
setup() {
const count = ref(0)
watchEffect(() => {
console.log(count.value)
})
return {
count,
}
},
}
</script>
在這個例子中:
-
count
會在初始運行時同步打印出來 - 更改
count
時,將在組件更新后執(zhí)行副作用爱致。
請注意烤送,初始化運行是在組件 mounted
之前執(zhí)行的。因此糠悯,如果你希望在編寫副作用函數(shù)時訪問 DOM(或模板 ref)帮坚,請在 onMounted
鉤子中進行:
onMounted(() => {
watchEffect(() => {
// 在這里可以訪問到 DOM 或者 template refs
})
})
如果副作用需要同步或在組件更新之前重新運行牢裳,我們可以傳遞一個擁有 flush
屬性的對象作為選項(默認為 'post'
):
// 同步運行
watchEffect(
() => {
/* ... */
},
{
flush: 'sync',
}
)
// 組件更新前執(zhí)行
watchEffect(
() => {
/* ... */
},
{
flush: 'pre',
}
)
#偵聽器調(diào)試
onTrack
和 onTrigger
選項可用于調(diào)試一個偵聽器的行為。
- 當(dāng)一個 reactive 對象屬性或一個 ref 作為依賴被追蹤時叶沛,將調(diào)用
onTrack
- 依賴項變更導(dǎo)致副作用被觸發(fā)時蒲讯,將調(diào)用
onTrigger
這兩個回調(diào)都將接收到一個包含有關(guān)所依賴項信息的調(diào)試器事件。建議在以下回調(diào)中編寫 debugger
語句來檢查依賴關(guān)系:
watchEffect(
() => {
/* 副作用的內(nèi)容 */
},
{
onTrigger(e) {
debugger
},
}
)
onTrack
和 onTrigger
僅在開發(fā)模式下生效灰署。
-
類型定義
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
#watch
watch
API 完全等效于 2.x this.$watch
(以及 watch
中相應(yīng)的選項)判帮。watch
需要偵聽特定的數(shù)據(jù)源,并在回調(diào)函數(shù)中執(zhí)行副作用溉箕。默認情況是懶執(zhí)行的晦墙,也就是說僅在偵聽的源變更時才執(zhí)行回調(diào)。
-
對比
watchEffect
肴茄,watch
允許我們:- 懶執(zhí)行副作用晌畅;
- 更明確哪些狀態(tài)的改變會觸發(fā)偵聽器重新運行副作用;
- 訪問偵聽狀態(tài)變化前后的值寡痰。
-
偵聽單個數(shù)據(jù)源
偵聽器的數(shù)據(jù)源可以是一個擁有返回值的 getter 函數(shù)抗楔,也可以是 ref:
// 偵聽一個 getter const state = reactive({ count: 0 }) watch( () => state.count, (count, prevCount) => { /* ... */ } ) // 直接偵聽一個 ref const count = ref(0) watch(count, (count, prevCount) => { /* ... */ })
-
偵聽多個數(shù)據(jù)源
watcher
也可以使用數(shù)組來同時偵聽多個源:watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => { /* ... */ })
-
與
watchEffect
共享的行為watch 和 watchEffect 在停止偵聽, 清除副作用 (相應(yīng)地
onInvalidate
會作為回調(diào)的第三個參數(shù)傳入),副作用刷新時機 和 偵聽器調(diào)試 等方面行為一致. -
類型定義
// 偵聽單數(shù)據(jù)源 function watch<T>( source: WatcherSource<T>, callback: ( value: T, oldValue: T, onInvalidate: InvalidateCbRegistrator ) => void, options?: WatchOptions ): StopHandle // 偵聽多數(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 } // 共有的屬性 請查看 `watchEffect` 的類型定義 interface WatchOptions extends WatchEffectOptions { immediate?: boolean // default: false deep?: boolean }
#生命周期鉤子函數(shù)
可以直接導(dǎo)入 onXXX
一族的函數(shù)來注冊生命周期鉤子:
import { onMounted, onUpdated, onUnmounted } from 'vue'
const MyComponent = {
setup() {
onMounted(() => {
console.log('mounted!')
})
onUpdated(() => {
console.log('updated!')
})
onUnmounted(() => {
console.log('unmounted!')
})
},
}
這些生命周期鉤子注冊函數(shù)只能在 setup()
期間同步使用拦坠, 因為它們依賴于內(nèi)部的全局狀態(tài)來定位當(dāng)前組件實例(正在調(diào)用 setup()
的組件實例), 不在當(dāng)前組件下調(diào)用這些函數(shù)會拋出一個錯誤连躏。
組件實例上下文也是在生命周期鉤子同步執(zhí)行期間設(shè)置的,因此贞滨,在卸載組件時入热,在生命周期鉤子內(nèi)部同步創(chuàng)建的偵聽器和計算狀態(tài)也將自動刪除。
-
與 2.x 版本生命周期相對應(yīng)的組合式 API
-
beforeCreate
-> 使用setup()
-
created
-> 使用setup()
-
beforeMount
->onBeforeMount
-
mounted
->onMounted
-
beforeUpdate
->onBeforeUpdate
-
updated
->onUpdated
-
beforeDestroy
->onBeforeUnmount
-
destroyed
->onUnmounted
-
errorCaptured
->onErrorCaptured
-
-
新增的鉤子函數(shù)
除了和 2.x 生命周期等效項之外晓铆,組合式 API 還提供了以下調(diào)試鉤子函數(shù):
onRenderTracked
onRenderTriggered
兩個鉤子函數(shù)都接收一個
DebuggerEvent
勺良,與watchEffect
參數(shù)選項中的onTrack
和onTrigger
類似:export default { onRenderTriggered(e) { debugger // 檢查哪個依賴性導(dǎo)致組件重新渲染 }, }
#依賴注入
provide
和 inject
提供依賴注入,功能類似 2.x 的 provide/inject
骄噪。兩者都只能在當(dāng)前活動組件實例的 setup()
中調(diào)用尚困。
import { provide, inject } from 'vue'
const ThemeSymbol = Symbol()
const Ancestor = {
setup() {
provide(ThemeSymbol, 'dark')
},
}
const Descendent = {
setup() {
const theme = inject(ThemeSymbol, 'light' /* optional default value */)
return {
theme,
}
},
}
inject
接受一個可選的的默認值作為第二個參數(shù)。如果未提供默認值腰池,并且在 provide 上下文中未找到該屬性尾组,則 inject
返回 undefined
忙芒。
-
注入的響應(yīng)性
可以使用
ref
來保證provided
和injected
之間值的響應(yīng):// 提供者: const themeRef = ref('dark') provide(ThemeSymbol, themeRef) // 使用者: const theme = inject(ThemeSymbol, ref('light')) watchEffect(() => { console.log(`theme set to: ${theme.value}`) })
如果注入一個響應(yīng)式對象示弓,則它的狀態(tài)變化也可以被偵聽。
-
類型定義
interface InjectionKey<T> extends Symbol {} function provide<T>(key: InjectionKey<T> | string, value: T): void // 未傳呵萨,使用缺省值 function inject<T>(key: InjectionKey<T> | string): T | undefined // 傳入了默認值 function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T
Vue 提供了一個繼承
Symbol
的InjectionKey
接口奏属。它可用于在提供者和消費者之間同步注入值的類型:import { InjectionKey, provide, inject } from 'vue' const key: InjectionKey<string> = Symbol() provide(key, 'foo') // 類型不是 string 則會報錯 const foo = inject(key) // foo 的類型: string | undefined
如果使用字符串作為鍵或沒有定義類型的符號,則需要顯式聲明注入值的類型:
const foo = inject<string>('foo') // string | undefined
#模板 Refs
當(dāng)使用組合式 API 時潮峦,reactive refs 和 template refs 的概念已經(jīng)是統(tǒng)一的囱皿。為了獲得對模板內(nèi)元素或組件實例的引用勇婴,我們可以像往常一樣在 setup()
中聲明一個 ref 并返回它:
<template>
<div ref="root"></div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup() {
const root = ref(null)
onMounted(() => {
// 在渲染完成后, 這個 div DOM 會被賦值給 root ref 對象
console.log(root.value) // <div/>
})
return {
root,
}
},
}
</script>
這里我們將 root
暴露在渲染上下文中,并通過 ref="root"
綁定到 div
作為其 ref
嘱腥。 在 Virtual DOM patch 算法中耕渴,如果一個 VNode 的 ref
對應(yīng)一個渲染上下文中的 ref,則該 VNode 對應(yīng)的元素或組件實例將被分配給該 ref齿兔。 這是在 Virtual DOM 的 mount / patch 過程中執(zhí)行的橱脸,因此模板 ref 僅在渲染初始化后才能訪問。
ref 被用在模板中時和其他 ref 一樣:都是響應(yīng)式的分苇,并可以傳遞進組合函數(shù)(或從其中返回)添诉。
-
配合 render 函數(shù) / JSX 的用法
export default { setup() { const root = ref(null) return () => h('div', { ref: root, }) // 使用 JSX return () => <div ref={root} /> }, }
-
在
v-for
中使用模板 ref 在
v-for
中使用 vue 沒有做特殊處理,需要使用函數(shù)型的 ref(3.0 提供的新功能)來自定義處理方式:<template> <div v-for="(item, i) in list" :ref="el => { divs[i] = el }"> {{ item }} </div> </template> <script> import { ref, reactive, onBeforeUpdate } from 'vue' export default { setup() { const list = reactive([1, 2, 3]) const divs = ref([]) // 確保在每次變更之前重置引用 onBeforeUpdate(() => { divs.value = [] }) return { list, divs, } }, } </script>
#響應(yīng)式系統(tǒng)工具集
#unref
如果參數(shù)是一個 ref 則返回它的 value
医寿,否則返回參數(shù)本身栏赴。它是 val = isRef(val) ? val.value : val
的語法糖。
function useFoo(x: number | Ref<number>) {
const unwrapped = unref(x) // unwrapped 一定是 number 類型
}
#toRef
toRef
可以用來為一個 reactive 對象的屬性創(chuàng)建一個 ref靖秩。這個 ref 可以被傳遞并且能夠保持響應(yīng)性须眷。
const state = reactive({
foo: 1,
bar: 2,
})
const fooRef = toRef(state, 'foo')
fooRef.value++
console.log(state.foo) // 2
state.foo++
console.log(fooRef.value) // 3
當(dāng)您要將一個 prop 中的屬性作為 ref 傳給組合邏輯函數(shù)時,toRef
就派上了用場:
export default {
setup(props) {
useSomeFeature(toRef(props, 'foo'))
},
}
#toRefs
把一個響應(yīng)式對象轉(zhuǎn)換成普通對象沟突,該普通對象的每個 property 都是一個 ref 柒爸,和響應(yīng)式對象 property 一一對應(yīng)。
const state = reactive({
foo: 1,
bar: 2,
})
const stateAsRefs = toRefs(state)
/*
stateAsRefs 的類型如下:
{
foo: Ref<number>,
bar: Ref<number>
}
*/
// ref 對象 與 原屬性的引用是 "鏈接" 上的
state.foo++
console.log(stateAsRefs.foo) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
當(dāng)想要從一個組合邏輯函數(shù)中返回響應(yīng)式對象時事扭,用 toRefs
是很有效的捎稚,該 API 讓消費組件可以 解構(gòu) / 擴展(使用 ...
操作符)返回的對象,并不會丟失響應(yīng)性:
function useFeatureX() {
const state = reactive({
foo: 1,
bar: 2,
})
// 對 state 的邏輯操作
// 返回時將屬性都轉(zhuǎn)為 ref
return toRefs(state)
}
export default {
setup() {
// 可以解構(gòu)求橄,不會丟失響應(yīng)性
const { foo, bar } = useFeatureX()
return {
foo,
bar,
}
},
}
#isRef
檢查一個值是否為一個 ref 對象今野。
#isProxy
檢查一個對象是否是由 reactive
或者 readonly
方法創(chuàng)建的代理。
#isReactive
檢查一個對象是否是由 reactive
創(chuàng)建的響應(yīng)式代理罐农。
如果這個代理是由 readonly
創(chuàng)建的条霜,但是又被 reactive
創(chuàng)建的另一個代理包裹了一層,那么同樣也會返回 true
涵亏。
#isReadonly
檢查一個對象是否是由 readonly
創(chuàng)建的只讀代理宰睡。
#高級響應(yīng)式系統(tǒng) API
#customRef
customRef
用于自定義一個 ref
,可以顯式地控制依賴追蹤和觸發(fā)響應(yīng)气筋,接受一個工廠函數(shù)拆内,兩個參數(shù)分別是用于追蹤的 track
與用于觸發(fā)響應(yīng)的 trigger
,并返回一個一個帶有 get
和 set
屬性的對象
-
使用自定義 ref 實現(xiàn)帶防抖功能的
v-model
:<input v-model="text" />
function useDebouncedRef(value, delay = 200) { let timeout return customRef((track, trigger) => { return { get() { track() return value }, set(newValue) { clearTimeout(timeout) timeout = setTimeout(() => { value = newValue trigger() }, delay) }, } }) } export default { setup() { return { text: useDebouncedRef('hello'), } }, }
-
類型定義
function customRef<T>(factory: CustomRefFactory<T>): Ref<T> type CustomRefFactory<T> = ( track: () => void, trigger: () => void ) => { get: () => T set: (value: T) => void }
#markRaw
顯式標(biāo)記一個對象為“永遠不會轉(zhuǎn)為響應(yīng)式代理”宠默,函數(shù)返回這個對象本身麸恍。
const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false
// 如果被 markRaw 標(biāo)記了,即使在響應(yīng)式對象中作屬性,也依然不是響應(yīng)式的
const bar = reactive({ foo })
console.log(isReactive(bar.foo)) // false
注意
markRaw
和下面的 shallowXXX 一族的 API 允許你可選擇性的覆蓋 reactive readonly 默認 "深層的" 特性抹沪,或者使用無代理的普通對象刻肄。設(shè)計這種「淺層讀取」有很多原因,比如:
- 一些值的實際上的用法非常簡單融欧,并沒有必要轉(zhuǎn)為響應(yīng)式敏弃,比如某個復(fù)雜的第三方類庫的實例,或者 Vue 組件對象
- 當(dāng)渲染一個元素數(shù)量龐大噪馏,但是數(shù)據(jù)是不可變的权她,跳過 Proxy 的轉(zhuǎn)換可以帶來性能提升。
這些 API 被認為是高級的逝薪,是因為這種特性僅停留在根級別隅要,所以如果你將一個嵌套的,沒有 markRaw
的對象設(shè)置為 reactive 對象的屬性董济,在重新訪問時步清,你又會得到一個 Proxy 的版本,在使用中最終會導(dǎo)致標(biāo)識混淆的嚴重問題:執(zhí)行某個操作同時依賴于某個對象的原始版本和代理版本虏肾。
const foo = markRaw({
nested: {},
})
const bar = reactive({
// 盡管 `foo` 己經(jīng)被標(biāo)記為 raw 了, 但 foo.nested 并沒有
nested: foo.nested,
})
console.log(foo.nested === bar.nested) // false
標(biāo)識混淆在一般使用當(dāng)中應(yīng)該是非常罕見的廓啊,但是要想完全避免這樣的問題,必須要對整個響應(yīng)式系統(tǒng)的工作原理有一個相當(dāng)清晰的認知封豪。
#shallowReactive
只為某個對象的私有(第一層)屬性創(chuàng)建淺層的響應(yīng)式代理谴轮,不會對“屬性的屬性”做深層次、遞歸地響應(yīng)式代理吹埠,而只是保留原樣第步。
const state = shallowReactive({
foo: 1,
nested: {
bar: 2,
},
})
// 變更 state 的自有屬性是響應(yīng)式的
state.foo++
// ...但不會深層代理
isReactive(state.nested) // false
state.nested.bar++ // 非響應(yīng)式
#shallowReadonly
只為某個對象的自有(第一層)屬性創(chuàng)建淺層的只讀響應(yīng)式代理,同樣也不會做深層次缘琅、遞歸地代理粘都,深層次的屬性并不是只讀的。
const state = shallowReadonly({
foo: 1,
nested: {
bar: 2,
},
})
// 變更 state 的自有屬性會失敗
state.foo++
// ...但是嵌套的對象是可以變更的
isReadonly(state.nested) // false
state.nested.bar++ // 嵌套屬性依然可修改
#shallowRef
創(chuàng)建一個 ref 刷袍,將會追蹤它的 .value
更改操作翩隧,但是并不會對變更后的 .value
做響應(yīng)式代理轉(zhuǎn)換(即變更不會調(diào)用 reactive
)
const foo = shallowRef({})
// 更改對操作會觸發(fā)響應(yīng)
foo.value = {}
// 但上面新賦的這個對象并不會變?yōu)轫憫?yīng)式對象
isReactive(foo.value) // false
#toRaw
返回由 reactive
或 readonly
方法轉(zhuǎn)換成響應(yīng)式代理的普通對象。這是一個還原方法呻纹,可用于臨時讀取堆生,訪問不會被代理/跟蹤,寫入時也不會觸發(fā)更改雷酪。不建議一直持有原始對象的引用淑仆。請謹慎使用。
const foo = {}
const reactiveFoo = reactive(foo)
console.log(toRaw(reactiveFoo) === foo) // true