前言
前面兩章,給大家講解了vue3
中ref
, reactive
,readonly
創建響應式數據的API, 以及常用的計算屬性computed
, 偵聽器watch
,watchEffect
的使用
其中reactive
, ref
, readonly
創建的響應式數據都是深層響應.
而本章主要給大家講解以上三個API 對應的創建淺層響應式數據的 API, 即shallowRef
, shallowReactive
, shallowReadonly
1. shallowRef
shallowRef
是ref
淺層作用形式。其實就是淺層響應式
shalloRef
的使用方式和ref()
完全相同, 不同的只是vue
對數據響應式處理的不同:
ref
創建的數據, 如果有深層, 比如參數是一個對象, 對象的屬性就是深層, 參數對象會被vue
通過reactive
處理為深層響應- 而
shallowRef
創建數據, 只有頂層的value
屬性具有響應性, 深層的數據不具有響應性, 會原因返回, 即vue
不會遞歸的將深層數據通過reactive
處理, 而是原樣存儲和暴露.
1.1. 類型
我們先看一下shallowRef
API 的類型簽名, 其簽名如下:
function shallowRef<T>(value: T): ShallowRef<T>interface ShallowRef<T> {value: T
}
通過簽名可以簡單的看出, shallowRef
API 接收一個參數, 返回一個具有value
屬性的對象, 且value
屬性的類型和參數類型相同.
1.2. ref 和shallowRef 處理原始數據類型
接下會通過示例帶大家看一下ref
和shallowRef
在處理原始數據類型的有什么不同.
示例代碼:
<template><div><h3>shallowRef</h3><div>{{ count }}</div><div>{{ count2 }}</div><button @click="change">修改數據源</button></div>
</template><script lang="ts">
import { defineComponent, ref, shallowRef } from 'vue'export default defineComponent({setup() {const count = ref(0)const count2 = shallowRef(0)// 控制臺輸出 ref, shallowRef 創建的數據consollog("count", count);console.log("count2", count2);// 修改數據const change = () => {// count.value++count2.value++}return { count, count2, change }}
})
</script>
控制臺輸出結果
通過代碼的運行結果, 你可以看出,
在參數為基本數據類型的情況下, ref
和shallowRef
創建的響應式數據在使用上完全一樣, value
屬性都具有響應性
通過控制臺輸出結果, 你會發現ref
和shallowRef
創建的ref
對象極度相似,
如果你閱讀過源碼, 你就會明白, ref
, shallowRef
返回對象是通過同一個類實例化的對象.因此兩個實例對象具有相同的屬性. 但是有一個屬性__v_isShallow
屬性值不同, 因為vue
通過這個屬性來區分是ref
還是shallowRef
創建的對象.
這里不對源碼實現做過多闡述, 如果你對源碼感興趣, 可以關注我的vue3
源碼專欄
1.3. ref 和shallowRef 處理深層對象
ref
和 shallowRef
在處理深層對象就會有所不同:
ref
函數在處理深層對象時, 深層對象會被vue
自動調用reactive
包裹成響應式數據,shallowRef
函數在處理深層對象時,vue
不同將深層對象包裹為響應式對象, 也就是說shallowRef
只有.value
屬性值才具有響應性, 深層對象不具有響應性
示例:
<template><div><h3>shallowRef</h3><div>{{ count }}</div><div>{{ count2 }}</div><button @click="change">修改數據源</button></div>
</template><script lang="ts">
import { defineComponent, ref, shallowRef } from 'vue'export default defineComponent({setup() {const count = ref({ count: 0 })const count2 = shallowRef({ count: 0 })// 控制輸出 ref, shallowRef 對于參數為對象時創建的ref 對象console.log('count', count)console.log('count2', count2)// 修改數據const change = () => {// count.value.count++// 不會觸發響應式// count2.value.count++// count2 是shallowRef數據// shallowRef 數據只有通過.value 整體修改時才會觸發響應式count2.value = { count: 3 }}return { count, count2, change }}
})
</script>
控制臺輸出:
通過控制臺輸出ref
, shallowRef
創建的響應數據, 以及示例的運行結果, 會發現:
shallowRef
在參數為深層對象時, 創建的ref
數據,value
值就是參數原對象, 不具有響應性- 但
ref
的value
屬性值, 是vue
調用reactive
函數包裹成的Proxy
代理對象, 即響應式數據
因此, 你可以理解shallowRef
是ref
淺層響應式的API, 只有通過.value
修改數據才會觸發響應式, 深層對象沒有通過reactive
包裹, 因此深層操作數據不具有響應式
shallowRef()
常常用于對大型數據結構的性能優化或是與外部的狀態管理系統集成。
2. shallowReactive
和shallowRef
與ref
關系相似,
shallowReactive
是reactive
淺層作用形式。就是創建具有淺層響應性的數據
2.1. shallowReactive 類型
首先,我們先看一下shallowReactive
類型簽名, 簽名如下:
function shallowReactive<T extends object>(target: T): T
通過簽名可以看出, shallowReactive
函數接收一個對象作為參數, 返回類型與參數類型相同
2.2. shallowReactive 淺層響應式
和 reactive()
不同,shallowReactive
創建響應對象沒有深層級的轉換:一個淺層響應式對象里只有根級別的屬性是響應式的, 深層對象不會被vue
自動包裝為響應對象.
示例:
<template><div><h3>shallowReactive</h3><div>{{ user }}</div><div>{{ user2 }}</div><button @click="change">修改數據源</button></div>
</template><script lang="ts">
import { defineComponent, reactive, shallowReactive } from 'vue'export default defineComponent({setup() {const user = reactive({ name: '張三', age: 18, friend: { name: '李四' } })const user2 = shallowReactive({ name: '張三', age: 18, friend: { name: '李四' } })// 控制臺輸出reactive, shallowReactive 創建的深層對象console.log("reactive friend", user.friend);console.log("shallowReactive friend", user2.friend);// 修改數據const change = () => {// 1. reactive, shallowReactive 在處理第一層對象屬性時// 都會觸發響應式// user.name = "王五" // 修改 reactive// user2.name = "王五" // 修改 shallowReactive// 2. 操作深度屬性,shallowReactive 不會觸發響應性// user.friend.name = '王五' // 修改 reactiveuser2.friend.name = '王五' // 修改 shallowReactive 不觸發響應式}return { user, user2, change }}
})
</script>
控制臺輸出結果:
通過控制臺輸出結果, 你應該已經看出:
reactive
創建的響應數據(代理對象) 深層對象也會自動的調用reactive
函數, 創建為響應數據shallowReactive
創建淺層響應數據, 其深層對象就是原樣的不同對象, 不具有響應性.
運行結果也可以看出, 修改shallowReactive
深層對象的數據, 頁面是不會有任何變化的.因為不具有響應性.
2.3. shallowReactive 深層ref 數據不會自動解包
shallowReactive()
函數創建一個淺層響應式對象里只有根級別的屬性是響應式的。
也可以說shallowReactive()
函數創建的數據是非深度監聽, 只會包裝第一個對象, 這也就意味著深層的ref
數據不會被自動解包.
因為shallowReactive
深層數據的存儲是原樣存儲, 不會包裹為深度響應式,
示例:
<template><div><h3>shallowReactive</h3><div>{{ user }}</div><div>{{ user2 }}</div><button @click="change">修改數據源</button></div>
</template><script lang="ts">
import { defineComponent, reactive, ref, shallowReactive } from 'vue'export default defineComponent({setup() {const count = ref(10)const user = reactive({ name: '張三', age: 18, count })const count2 = ref(20)const user2 = shallowReactive({ name: '張三', age: 18, count: count2 })// 修改數據const change = () => {// 1. reactive 操作深層ref 數據時,自動解包// 此時修改user.count 數據時不用添加.value// user.count = 20 // 修改 reactive// 2. shallowReactive 操作深層ref 數據時,不會自動解包// 此時修改user2.count 數據時必須使用.valueuser2.count.value = 40 // 修改 shallowReactive}return { user, user2, change }}
})
</script>
2.4. shallowReactive與shallowRef 使用比較
- 一般情況下使用
ref
和reactive
即可 - 如果有一個對象數據, 結構比較深, 但只有一層對象的屬性變化會觸發響應, 使用
shallowReactive
- 如果有一個對象數據, 后面會產生新的對象來整體替換觸發響應式, 使用
shallowRef
3. shallowReadonly
shallowReadonly
是readonly
淺層作用形式。只有第一層是只讀的,深層不是只讀屬性,
也可以這么理解, 只有第一層的對象屬性不能修改值, 但深層的數據是可以修改的, 是非深層只讀
3.1. shallowReadonly 淺層只讀
shallowReadonly
在使用上與readonly
完全相同, 區域在于:
readonly()
創建只讀代理, 如果有深層對象, 深層對象也會自動調用readonly
處理為只讀代理shallowReadonly
創建的是淺層只讀代理, 也就是深層對象不會自動調用readonly
包裹, 所以深層對象是非只讀的, 即可以修改的. 也就意味著只有根層級的屬性變為了只讀。
示例:
<template><div><h3>shallowReadonly</h3><div>{{ user }}</div><div>{{ user2 }}</div><button @click="change">修改數據源</button></div>
</template><script lang="ts">
import { defineComponent, readonly, ref, shallowReadonly } from 'vue'export default defineComponent({setup() {const user = readonly({ name: '張三', age: 18, friend: { name: '李四' } })const user2 = shallowReadonly({ name: '張三', age: 18, friend: { name: '李四' } })// 修改數據const change = () => {// readonly 為深層只讀, 修改任何一層屬性值都是不合法// user.name = '王五'// user.friend.name = '王五'// shallowReadonly 只有第一層會被轉為只讀, 深層屬性會原樣存儲,不是只讀的// user2.name = '王五' // shallowReadonly 第一層修改報錯,因為是只讀的// shallowReadonly 修改生成不會報錯,// 但也不會觸發響應性, 因為原樣存儲就是一個普通對象user2.friend.name = '王五'}return { user, user2, change }}
})
</script>
3.2. shallowReadonly 處理深層ref不會自動解包
因為shallowReadonly
深層數據的存儲是原樣存儲, 不會自動調用shallowReadonly
轉為只讀, 因此對于深層的ref
的數據不會被自動解包了。
示例:
<template><div><h3>shallowReadonly</h3><div>{{ user }}</div><div>{{ user2 }}</div></div>
</template><script lang="ts">
import { defineComponent, readonly, ref, shallowReadonly } from 'vue'export default defineComponent({setup() {const count = ref(10)const user = readonly({ name: '張三', age: 18, count })const count2 = ref(20)const user2 = shallowReadonly({ name: '張三', age: 18, count: count2 })// readonly 處理深層數據為ref 數據時, 會自動解包,不用添加.valueconsole.log('user.count', user.count)// shallowReadonly 獲取深層ref 數據時必須添加.value, 因為不會自動解包console.log('user2.count', user2.count.value)return { user, user2 }}
})
</script>
4. 結語
至此, 就把shallowRef
, shallowReactive
,shallowReadonly
給大家講解完了, 這三個API使用上還是相對較少. 大家要理解這些API的使用原理, 工作中根據情況選擇不同的API .