vue3響應(yīng)式API之ref的使用
vue3響應(yīng)式API之ref使用
ref 是最常用的一個(gè)響應(yīng)式 API,它可以用來(lái)定義所有類型的數(shù)據(jù),包括 Node 節(jié)點(diǎn)和組件。
沒(méi)錯(cuò),在 Vue 2 常用的 this.$refs.xxx 來(lái)取代 document.querySelector(‘.xxx’) 獲取 Node 節(jié)點(diǎn)的方式,也是使用這個(gè) API 來(lái)取代。
類型聲明
在開始使用 API 之前,需要先了解在 TypeScript 中如何聲明 Ref 變量的類型。
API 本身的類型
先看 API 本身, ref API 是一個(gè)函數(shù),通過(guò)接受一個(gè)泛型入?yún)ⅲ祷匾粋€(gè)響應(yīng)式對(duì)象,所有的值都通過(guò) .value 屬性獲取,這是 API 本身的 TS 類型:
// `ref` API 的 TS 類型
function ref<T>(value: T): Ref<UnwrapRef<T>>
// `ref` API 的返回值的 TS 類型
interface Ref<T> {
value: T
}
因此在聲明變量時(shí),是使用尖括號(hào) <> 包裹其 TS 類型,緊跟在 ref API 之后:
// 顯式指定 `msg.value` 是 `string` 類型 const msg=ref<string>('hello')
再回看該 API 本身的類型,其中使用了 T 泛型,這表示在傳入函數(shù)的入?yún)r(shí),可以不需要手動(dòng)指定其 TS 類型, TypeScript 會(huì)根據(jù)這個(gè) API 所返回的響應(yīng)式對(duì)象的 .value 屬性的類型,確定當(dāng)前變量的類型。
因此也可以省略顯式的類型指定,像下面這樣聲明變量,其類型交給 TypeScript 去自動(dòng)推導(dǎo):
// TypeScript 會(huì)推導(dǎo) `msg.value` 是 `string` 類型
const msg = ref('Hello World')
對(duì)于聲明時(shí)會(huì)賦予初始值,并且在使用過(guò)程中不會(huì)改變其類型的變量,是可以省略類型的顯式指定的。
而如果有顯式的指定的類型,那么在一些特殊情況下,初始化時(shí)可以不必賦值,這樣 TypeScript 會(huì)自動(dòng)添加 undefined 類型:
const msg = ref<string>() console.log(msg.value) // undefined msg.value = 'Hello World!' console.log(msg.value) // Hello World!
因?yàn)槿雲(yún)⒘艨諘r(shí),雖然指定了 string 類型,但實(shí)際上此時(shí)的值是 undefined ,因此實(shí)際上這個(gè)時(shí)候的 msg.value 是一個(gè) string | undefined 的聯(lián)合類型。
對(duì)于聲明時(shí)不知道是什么值,在某種條件下才進(jìn)行初始化的情況,就可以省略其初始值,但是切記在調(diào)用該變量的時(shí)候?qū)?.value 值進(jìn)行有效性判斷。
而如果既不顯式指定類型,也不賦予初始值,那么會(huì)被默認(rèn)為 any 類型,除非真的無(wú)法確認(rèn)類型,否則不建議這么做。
API 返回值的類型
細(xì)心的開發(fā)者還會(huì)留意到 ref API 類型里面還標(biāo)注了一個(gè)返回值的 TS 類型:
interface Ref<T> {
value: T
}
它是代表整個(gè) Ref 變量的完整類型:
上文聲明 Ref 變量時(shí),提到的 string 類型都是指 msg.value 這個(gè) .value 屬性的類型
而 msg 這個(gè)響應(yīng)式變量,其本身是 Ref 類型
如果在開發(fā)過(guò)程中需要在函數(shù)里返回一個(gè) Ref 變量,那么其 TypeScript 類型就可以這樣寫(請(qǐng)留意 Calculator 里的 num 變量的類型):
// 導(dǎo)入 `ref` API
import { ref } from 'vue'
// 導(dǎo)入 `ref` API 的返回值類型
import type { Ref } from 'vue'
// 聲明 `useCalculator` 函數(shù)的返回值類型
interface Calculator {
// 這里包含了一個(gè) Ref 變量
num: Ref<number>
add: () => void
}
// 聲明一個(gè) “使用計(jì)算器” 的函數(shù)
function useCalculator(): Calculator {
const num = ref<number>(0)
function add() {
num.value++
}
return {
num,
add,
}
}
// 在執(zhí)行使用計(jì)算器函數(shù)時(shí),可以獲取到一個(gè) Ref 變量和其他方法
const { num, add } = useCalculator()
add()
console.log(num.value) // 1
上面這個(gè)簡(jiǎn)單的例子演示了如何手動(dòng)指定 Ref 變量的類型,對(duì)于邏輯復(fù)用時(shí)的函數(shù)代碼抽離、插件開發(fā)等場(chǎng)景非常有用!
當(dāng)然大部分情況下可以交給 TypeScript 自動(dòng)推導(dǎo),但掌握其用法,在必要的時(shí)候就派得上用場(chǎng)了!
變量的定義
在了解了如何對(duì) Ref 變量進(jìn)行類型聲明之后,面對(duì)不同的數(shù)據(jù)類型,相信都得心應(yīng)手了!
但不同類型的值之間還是有少許差異和注意事項(xiàng),例如上文提及到該 API 可以用來(lái)定義所有類型的數(shù)據(jù),包括 Node 節(jié)點(diǎn)和組件,具體可以參考下文的示例。
基本類型
對(duì)字符串、布爾值等基本類型的定義方式,比較簡(jiǎn)單:
//字符串
const msg = ref<string>('123')
//數(shù)值
const count =ref<number>(1)
//布爾值
const isVip = ref<boolean>(false)
引用類型
對(duì)于對(duì)象、數(shù)組等引用類型也適用,比如要定義一個(gè)對(duì)象:
// 先聲明對(duì)象的格式
interface Member {
id: number
name: string
}
// 在定義對(duì)象時(shí)指定該類型
const userInfo = ref<Member>({
id: 1,
name: 'Tom',
})
定義一個(gè)普通數(shù)組:
const uids =ref<number[]>([1,2,3]) //字符串?dāng)?shù)組 const names = ref<string[]>(['Tom', 'Petter', 'Andy'])
定義一個(gè)對(duì)象數(shù)組:
//聲明對(duì)象的格式
interface Member{
id:number
name:string
}
//定義一個(gè)對(duì)象數(shù)組
const memberList = ref<Member[]>([
{
id: 1,
name: 'Tom',
},
{
id: 2,
name: 'Petter',
},
])
DOM 元素與子組件
除了可以定義數(shù)據(jù),ref 也有熟悉的用途,就是用來(lái)掛載節(jié)點(diǎn),也可以掛在子組件上,也就是對(duì)應(yīng)在 Vue 2 時(shí)常用的 this.$refs.xxx 獲取 DOM 元素信息的作用。
模板部分依然是熟悉的用法,在要引用的 DOM 上添加一個(gè) ref 屬性:
<template> <!-- 給 DOM 元素添加 `ref` 屬性 --> <p ref="msg">請(qǐng)留意該節(jié)點(diǎn),有一個(gè) ref 屬性</p> <!-- 子組件也是同樣的方式添加 --> <Child ref="child" /> </template>
在 代碼里添加的 ref 屬性的值,是對(duì)應(yīng)
請(qǐng)保證視圖渲染完畢后再執(zhí)行 DOM 或組件的相關(guān)操作(需要放到生命周期的 onMounted 或者 nextTick 函數(shù)里,這一點(diǎn)在 Vue 2 也是一樣);
該 Ref 變量必須 return 出去才可以給到 使用,這一點(diǎn)是 Vue 3 生命周期的硬性要求,子組件的數(shù)據(jù)和方法如果要給父組件操作,也要 return 出來(lái)才可以。
配合上面的 ,來(lái)看看
import { defineComponent, onMounted, ref } from 'vue'
import Child from '@cp/Child.vue'
export default defineComponent({
components: {
Child,
},
setup() {
// 定義掛載節(jié)點(diǎn),聲明的類型詳見(jiàn)下方附表
const msg = ref<HTMLElement>()
const child = ref<typeof Child>()
// 請(qǐng)保證視圖渲染完畢后再執(zhí)行節(jié)點(diǎn)操作 e.g. `onMounted` / `nextTick`
onMounted(() => {
// 比如獲取 DOM 的文本
console.log(msg.value.innerText)
// 或者操作子組件里的數(shù)據(jù)
child.value.isShowDialog = true
})
// 必須 `return` 出去才可以給到 `<template />` 使用
return {
msg,
child,
}
},
})
關(guān)于 DOM 和子組件的 TS 類型聲明,可參考以下規(guī)則:

另外,關(guān)于這一小節(jié),有一個(gè)可能會(huì)引起 TS 編譯報(bào)錯(cuò)的情況是,一些腳手架創(chuàng)建出來(lái)的項(xiàng)目會(huì)默認(rèn)啟用 --strictNullChecks 選項(xiàng),會(huì)導(dǎo)致案例中的代碼無(wú)法正常編譯,出現(xiàn)如下報(bào)錯(cuò):
? npm run build
hello-vue3@0.0.0 build
vue-tsc --noEmit && vite buildsrc/views/home.vue:27:7 - error TS2532: Object is possibly 'undefined'.
27 child.value.isShowDialog = true
~~~~~~~~~~~
Found 1 error in src/views/home.vue:27
這是因?yàn)樵谀J(rèn)情況下 null 和 undefined 是所有類型的子類型,但開啟了 strictNullChecks 選項(xiàng)之后,會(huì)使 null 和 undefined 只能賦值給 void 和它們各自,這是一個(gè)更為嚴(yán)謹(jǐn)?shù)倪x項(xiàng),可以保障程序代碼的健壯性,但對(duì)于剛接觸 TypeScript 不久的開發(fā)者可能不太友好。
有以下幾種解決方案可以參考:
在涉及到相關(guān)操作的時(shí)候,對(duì)節(jié)點(diǎn)變量增加一個(gè)判斷:
// 添加 `if` 分支,判斷 `.value` 存在時(shí)才執(zhí)行相關(guān)代碼
if (child.value) {
// 讀取子組件的數(shù)據(jù)
console.log(child.value.num)
// 執(zhí)行子組件的方法
child.value.sayHi('Use `if` in `onMounted` API.')
}
通過(guò) TS 的可選符 ? 將目標(biāo)設(shè)置為可選,避免出現(xiàn)錯(cuò)誤(這個(gè)方式不能直接修改子組件數(shù)據(jù)的值):
// 讀取子組件的數(shù)據(jù)(留意 `.num` 前面有一個(gè) `?` 問(wèn)號(hào))
console.log(child.value?.num)
// 執(zhí)行子組件的方法(留意 `.sayHi` 前面有一個(gè) `?` 問(wèn)號(hào))
child.value?.sayHi('use ? in onMounted')
在項(xiàng)目根目錄下的 tsconfig.json 文件里,顯式的關(guān)閉 strictNullChecks 選項(xiàng),關(guān)閉后,需要開發(fā)者在寫代碼的時(shí)候,自行把控好是否需要對(duì) null 和 undefined 進(jìn)行判斷
{
"compilerOptions": {
// ...
"strictNullChecks": false
}
// ...
}
使用 any 類型代替,但是寫 TypeScript 還是盡量不要使用 any ,滿屏的 AnyScript 不如直接使用 JavaScript
變量的讀取與賦值
前面在介紹 API 類型的時(shí)候已經(jīng)了解,通過(guò) ref 聲明的變量會(huì)全部變成對(duì)象,不管定義的是什么類型的值,都會(huì)轉(zhuǎn)化為一個(gè) Ref 對(duì)象,其中 Ref 對(duì)象具有指向內(nèi)部值的單個(gè) Property .value。
也就是說(shuō),任何 Ref 對(duì)象的值都必須通過(guò) xxx.value 才可以正確獲取。
請(qǐng)牢記上面這句話,初擁 Vue 3 的開發(fā)者很多 BUG 都是由于這個(gè)問(wèn)題引起的(包括筆者剛開始使用 Vue 3 的那段時(shí)間,嘿嘿)。
讀取變量
平時(shí)對(duì)于普通變量的值,讀取的時(shí)候都是直接調(diào)用其變量名即可:
// 讀取一個(gè)字符串 const msg: string = 'Hello World!' console.log(msg) // 讀取一個(gè)數(shù)組 const uids: number[] = [1, 2, 3] console.log(uids[1])
而 Ref 對(duì)象的值的讀取,切記!必須通過(guò) .value !
// 讀取一個(gè)字符串
const msg = ref<string>('Hello World!')
console.log(msg.value)
// 讀取一個(gè)數(shù)組
const uids = ref<number[]>([1, 2, 3])
console.log(uids.value[1])
為變量賦值
普通變量需要使用 let 聲明才可以修改其值,由于 Ref 對(duì)象是個(gè)引用類型,所以可以使用 const 聲明,直接通過(guò) .value 修改。
// 聲明一個(gè)字符串變量
const msg = ref<string>('Hi!')
// 等待 1s 后修改它的值
setTimeout(() => {
msg.value = 'Hello!'
}, 1000)
因此日常業(yè)務(wù)中,像在對(duì)接服務(wù)端 API 的接口數(shù)據(jù)時(shí),可以自由的使用 forEach、map、filter 等方法操作 Ref 數(shù)組,或者直接重置它,而不必?fù)?dān)心數(shù)據(jù)失去響應(yīng)性。
const data = ref<string[]>([]) // 提取接口的數(shù)據(jù) data.value = api.data.map((item: any) => item.text) // 重置數(shù)組 data.value = []
為什么突然要說(shuō)這個(gè)呢?因?yàn)樯婕暗较乱徊糠值闹R(shí),關(guān)于 reactive API 在使用上的注意事項(xiàng)。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
vue關(guān)于select組件綁定的值為數(shù)字類型的問(wèn)題
這篇文章主要介紹了vue關(guān)于select組件綁定的值為數(shù)字類型的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09
vue 中 beforeRouteEnter 死循環(huán)的問(wèn)題
這篇文章主要介紹了vue beforeRouteEnter 死循環(huán)的問(wèn)題,在文章末尾給大家補(bǔ)充介紹了vue中beforeRouteEnter使用的誤區(qū),需要的朋友可以參考下2019-04-04
vue判斷內(nèi)容是否滑動(dòng)到底部的三種方式
這篇文章主要介紹了vue判斷內(nèi)容是否滑動(dòng)到底部的三種方式,文中通過(guò)代碼示例給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-04-04
vue實(shí)現(xiàn)點(diǎn)擊追加選中樣式效果
今天小編就為大家分享一篇vue實(shí)現(xiàn)點(diǎn)擊追加選中樣式效果,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-11-11
VUE 解決mode為history頁(yè)面為空白的問(wèn)題
今天小編就為大家分享一篇VUE 解決mode為history頁(yè)面為空白的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-11-11
淺談Vue3.0新版API之composition-api入坑指南
這篇文章主要介紹了Vue3.0新版API之composition-api入坑指南,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
vue watch監(jiān)聽(tīng)數(shù)據(jù)變化的案例詳解
監(jiān)聽(tīng)數(shù)據(jù)變化,在Vue中是通過(guò)偵聽(tīng)器來(lái)實(shí)現(xiàn)的,你也可以將它理解為監(jiān)聽(tīng)器,時(shí)刻監(jiān)聽(tīng)某個(gè)數(shù)據(jù)的變化,本文將通過(guò)代碼示例為大家詳細(xì)的介紹一下vue watch如何監(jiān)聽(tīng)數(shù)據(jù)變化,需要的朋友可以參考下2023-07-07

