第六节:带你全面理解vue3 浅层响应式API: shallowRef, shallowReactive, shallowReadonly

前言

前面两章,给大家讲解了vue3ref, reactive,readonly创建响应式数据的API, 以及常用的计算属性computed, 侦听器watch,watchEffect的使用

其中reactive, ref, readonly创建的响应式数据都是深层响应.

而本章主要给大家讲解以上三个API 对应的创建浅层响应式数据的 API, 即shallowRef, shallowReactive, shallowReadonly

1. shallowRef

shallowRefref浅层作用形式。其实就是浅层响应式

shalloRef的使用方式和ref()完全相同, 不同的只是vue对数据响应式处理的不同:

  • ref创建的数据, 如果有深层, 比如参数是一个对象, 对象的属性就是深层, 参数对象会被vue通过reactive处理为深层响应
  • shallowRef创建数据, 只有顶层的value属性具有响应性, 深层的数据不具有响应性, 会原因返回, 即vue不会递归的将深层数据通过reactive处理, 而是原样存储和暴露.

1.1. 类型

我们先看一下shallowRefAPI 的类型签名, 其签名如下:

function shallowRef<T>(value: T): ShallowRef<T>

interface ShallowRef<T> {
  value: T
}

通过签名可以简单的看出, shallowRefAPI 接收一个参数, 返回一个具有value属性的对象, 且value属性的类型和参数类型相同.

1.2. ref 和shallowRef 处理原始数据类型

接下会通过示例带大家看一下refshallowRef 在处理原始数据类型的有什么不同.

示例代码:

<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>

控制台输出结果

image.png

通过代码的运行结果, 你可以看出,

在参数为基本数据类型的情况下, refshallowRef创建的响应式数据在使用上完全一样, value属性都具有响应性

通过控制台输出结果, 你会发现refshallowRef创建的ref对象极度相似,
如果你阅读过源码, 你就会明白, ref, shallowRef返回对象是通过同一个类实例化的对象.因此两个实例对象具有相同的属性. 但是有一个属性__v_isShallow属性值不同, 因为vue通过这个属性来区分是ref还是shallowRef创建的对象.

这里不对源码实现做过多阐述, 如果你对源码感兴趣, 可以关注我的vue3源码专栏


1.3. ref 和shallowRef 处理深层对象

refshallowRef 在处理深层对象就会有所不同:

  • 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>

控制台输出:

image.png

通过控制台输出ref, shallowRef创建的响应数据, 以及示例的运行结果, 会发现:

  • shallowRef在参数为深层对象时, 创建的ref数据, value值就是参数原对象, 不具有响应性
  • refvalue属性值, 是vue调用reactive函数包裹成的Proxy代理对象, 即响应式数据

因此, 你可以理解shallowRefref浅层响应式的API, 只有通过.value修改数据才会触发响应式, 深层对象没有通过reactive包裹, 因此深层操作数据不具有响应式

shallowRef() 常常用于对大型数据结构的性能优化或是与外部的状态管理系统集成。


2. shallowReactive

shallowRefref关系相似,

shallowReactivereactive浅层作用形式。就是创建具有浅层响应性的数据


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 = '王五'  // 修改 reactive
      user2.friend.name = '王五'  // 修改 shallowReactive  不触发响应式

    }
    return { user, user2, change }
  }
})
</script>

控制台输出结果:

image.png

通过控制台输出结果, 你应该已经看出:

  • 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 数据时必须使用.value
      user2.count.value = 40  // 修改 shallowReactive


    }
    return { user, user2, change }
  }
})
</script>


2.4. shallowReactive与shallowRef 使用比较

  1. 一般情况下使用refreactive即可
  2. 如果有一个对象数据, 结构比较深, 但只有一层对象的属性变化会触发响应, 使用shallowReactive
  3. 如果有一个对象数据, 后面会产生新的对象来整体替换触发响应式, 使用shallowRef

3. shallowReadonly

shallowReadonlyreadonly浅层作用形式。只有第一层是只读的,深层不是只读属性,

也可以这么理解, 只有第一层的对象属性不能修改值, 但深层的数据是可以修改的, 是非深层只读

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 数据时, 会自动解包,不用添加.value
    console.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 .

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容