当前位置: 首页 > news >正文

Vue3中的ref与reactive区别

刚学vue3的时候,每次用ref取值赋值都要用到“.value”后缀,认为非常麻烦,于是乎直接一直用reactive,可是工作后发现项目中几乎都用ref,这时就有点纳闷,为什么呢,来和我一起了解了解吧。

reactive()

基本用法

在 Vue3 中我们可以使用 reactive() 创建一个响应式对象或数组:

import { reactive } from 'vue'const state = reactive({ count: 0 })

这个响应式对象其实就是一个 Proxy, Vue 会在这个 Proxy 的属性被访问时收集副作用,属性被修改时触发副作用。

要在组件模板中使用响应式状态,需要在 setup() 函数中定义并返回。

xml复制代码<script>
import { reactive } from 'vue'export default {setup() {const state = reactive({ count: 0 })return {state}}
}
</script><template><div>{{ state.count }}</div>
</template>

当然,也可以使用 <script setup><script setup> 中顶层的导入和变量声明可以在模板中直接使用。

<script setup>
import { reactive } from 'vue'const state = reactive({ count: 0 })
</script><template><div>{{ state.count }}</div>
</template>

响应式代理 vs 原始对象

reactive() 返回的是一个原始对象的 Proxy,他们是不相等的:

const raw = {}
const proxy = reactive(raw)console.log(proxy === raw) // false

原始对象在模板中也是可以使用的,但修改原始对象不会触发更新。因此,要使用 Vue 的响应式系统,就必须使用代理。

<script setup>
const state = { count: 0 }
function add() {state.count++
}
</script><template><button @click="add">{{ state.count }} <!-- 当点击button时,始终显示为 0 --></button>
</template>

为保证访问代理的一致性,对同一个原始对象调用 reactive() 会总是返回同样的代理对象,而对一个已存在的代理对象调用 reactive() 会返回其本身:

js复制代码const raw = {}
const proxy1 = reactive(raw)
const proxy2 = reactive(raw)console.log(proxy1 === proxy2) // trueconsole.log(reactive(proxy1) === proxy1) // true

这个规则对嵌套对象也适用。依靠深层响应性,响应式对象内的嵌套对象依然是代理:

js复制代码const raw = {}
const proxy = reactive({ nested: raw })
const nested = reactive(raw)console.log(proxy.nested === nested) // true

shallowReactive()

在 Vue 中,状态默认都是深层响应式的。但某些场景下,我们可能想创建一个 浅层响应式对象 ,让它仅在顶层具有响应性,这时候可以使用 shallowReactive()

const state = shallowReactive({foo: 1,nested: {bar: 2}
})// 状态自身的属性是响应式的
state.foo++// 下层嵌套对象不是响应式的,不会按期望工作
state.nested.bar++

注意:浅层响应式对象应该只用于组件中的根级状态。避免将其嵌套在深层次的响应式对象中,因为其内部的属性具有不一致的响应行为,嵌套之后将很难理解和调试。

reactive() 的局限性

reactive() 虽然强大,但也有以下几条限制:

  1. 仅对对象类型有效(对象、数组和 MapSet 这样的集合类型),而对 stringnumberboolean 这样的原始类型无效。

  2. 因为 Vue 的响应式系统是通过属性访问进行追踪的,如果我们直接“替换”一个响应式对象,这会导致对初始引用的响应性连接丢失:

    xml复制代码<script setup>
    import { reactive } from 'vue'let state = reactive({ count: 0 })
    function change() {// 非响应式替换state = reactive({ count: 1 })
    }
    </script><template><button @click="change">{{ state }} <!-- 当点击button时,始终显示为 { "count": 0 } --></button>
    </template>
    
  3. 将响应式对象的属性赋值或解构至本地变量,或是将该属性传入一个函数时,会失去响应性:

    js复制代码const state = reactive({ count: 0 })// n 是一个局部变量,和 state.count 失去响应性连接
    let n = state.count
    // 不会影响 state
    n++// count 也和 state.count 失去了响应性连接
    let { count } = state
    // 不会影响 state
    count++// 参数 count 同样和 state.count 失去了响应性连接
    function callSomeFunction(count) {// 不会影响 statecount++
    }
    callSomeFunction(state.count)
    

为了解决以上几个限制,ref 闪耀登场了!

ref()

Vue 提供了一个 ref() 方法来允许我们创建使用任何值类型的响应式 ref 。

基本用法

ref() 将传入的参数包装为一个带有 value 属性的 ref 对象:

js复制代码import { ref } from 'vue'const count = ref(0)console.log(count) // { value: 0 }count.value++
console.log(count.value) // 1

和响应式对象的属性类似,ref 的 value 属性也是响应式的。同时,当值为对象类型时,Vue 会自动使用 reactive() 处理这个值。

一个包含对象的 ref 可以响应式地替换整个对象:

js复制代码<script setup>
import { ref } from 'vue'let state = ref({ count: 0 })
function change() {// 这是响应式替换state.value = ref({ count: 1 })
}
</script><template><button @click="change">{{ state }} <!-- 当点击button时,显示为 { "count": 1 } --></button>
</template>

ref 从一般对象上解构属性或将属性传递给函数时,不会丢失响应性:

js复制代码const state = {count: ref(0)
}
// 解构之后,和 state.count 依然保持响应性连接
const { count } = state
// 会影响 state
count.value++// 该函数接收一个 ref, 和传入的值保持响应性连接
function callSomeFunction(count) {// 会影响 statecount.value++
}
callSomeFunction(state.count)

ref() 让我们能创建使用任何值类型的 ref 对象,并能够在不丢失响应性的前提下传递这些对象。这个功能非常重要,经常用于将逻辑提取到 组合式函数 中。

js复制代码// mouse.js
export function useMouse() {const x = ref(0)const y = ref(0)// ...return { x, y }
}
js复制代码<script setup>
import { useMouse } from './mouse.js'
// 可以解构而不会失去响应性
const { x, y } = useMouse()
</script>

ref 的解包

所谓解包就是获取到 ref 对象上 value 属性的值。常用的两种方法就是 .valueunref()unref() 是 Vue 提供的方法,如果参数是 ref ,则返回 value 属性的值,否则返回参数本身。

ref 在模板中的解包

当 ref 在模板中作为顶层属性被访问时,它们会被自动解包,不需要使用 .value 。下面是之前的例子,使用 ref() 代替:

xml复制代码<script setup>
import { ref } from 'vue'const count = ref(0)
</script><template><div>{{ count }} <!-- 无需 .value --></div>
</template>

还有一种情况,如果文本插值({{ }})计算的最终值是 ref ,也会被自动解包。下面的非顶层属性会被正确渲染出来。

xml复制代码<script setup>
import { ref } from 'vue'const object = { foo: ref(1) }</script><template><div>{{ object.foo }} <!-- 无需 .value --></div>
</template>

其他情况则不会被自动解包,如:object.foo 不是顶层属性,文本插值({{ }})计算的最终值也不是 ref:

js
复制代码const object = { foo: ref(1) }

下面的内容将不会像预期的那样工作:

css
复制代码<div>{{ object.foo + 1 }}</div>

渲染的结果会是 [object Object]1,因为 object.foo 是一个 ref 对象。我们可以通过将 foo 改成顶层属性来解决这个问题:

js复制代码const object = { foo: ref(1) }
const { foo } = object
css
复制代码<div>{{ foo + 1 }}</div>

现在结果就可以正确地渲染出来了。

ref 在响应式对象中的解包

当一个 ref 被嵌套在一个响应式对象中,作为属性被访问或更改时,它会自动解包,因此会表现得和一般的属性一样:

js复制代码const count = ref(0)
const state = reactive({ count })console.log(state.count) // 0state.count = 1
console.log(state.count) // 1

只有当嵌套在一个深层响应式对象内时,才会发生解包。当 ref 作为 浅层响应式对象 的属性被访问时则不会解包:

scss复制代码const count = ref(0)
const state = shallowReactive({ count })console.log(state.count) // { value: 0 } 而不是 0

如果将一个新的 ref 赋值给一个已经关联 ref 的属性,那么它会替换掉旧的 ref:

js复制代码const count = ref(1)
const state = reactive({ count })const otherCount = ref(2)
state.count = otherCountconsole.log(state.count) // 2
// 此时 count 已经和 state.count 失去连接
console.log(count.value) // 1
ref 在数组和集合类型的解包

跟响应式对象不同,当 ref 作为响应式数组或像 Map 这种原生集合类型的元素被访问时,不会进行解包。

js复制代码const books = reactive([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books[0].value)const map = reactive(new Map([['count', ref(0)]]))
// 这里需要 .value
console.log(map.get('count').value)

toRef()

toRef 是基于响应式对象上的一个属性,创建一个对应的 ref 的方法。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。

const state = reactive({foo: 1,bar: 2
})const fooRef = toRef(state, 'foo')// 更改源属性会更新该 ref
state.foo++
console.log(fooRef.value) // 2// 更改该 ref 也会更新源属性
fooRef.value++
console.log(state.foo) // 3

toRef() 在你想把一个 prop 的 ref 传递给一个组合式函数时会很有用:

xml复制代码<script setup>
import { toRef } from 'vue'const props = defineProps(/* ... */)// 将 `props.foo` 转换为 ref,然后传入一个组合式函数
useSomeFeature(toRef(props, 'foo'))
</script>

toRef 与组件 props 结合使用时,关于禁止对 props 做出更改的限制依然有效。如果将新的值传递给 ref 等效于尝试直接更改 props,这是不允许的。在这种场景下,你可以考虑使用带有 getsetcomputed 替代。

注意:即使源属性当前不存在,toRef() 也会返回一个可用的 ref。这让它在处理可选 props 的时候非常有用,相比之下 toRefs 就不会为可选 props 创建对应的 refs 。下面我们就来了解一下 toRefs

toRefs()

toRefs() 是将一个响应式对象上的所有属性都转为 ref ,然后再将这些 ref 组合为一个普通对象的方法。这个普通对象的每个属性和源对象的属性保持同步。

js复制代码const state = reactive({foo: 1,bar: 2
})// 相当于
// const stateAsRefs = {
//   foo: toRef(state, 'foo'),
//   bar: toRef(state, 'bar')
// }
const stateAsRefs = toRefs(state)state.foo++
console.log(stateAsRefs.foo.value) // 2stateAsRefs.foo.value++
console.log(state.foo) // 3

从组合式函数中返回响应式对象时,toRefs 相当有用。它可以使我们解构返回的对象时,不失去响应性:

js复制代码// feature.js
export function useFeature() {const state = reactive({foo: 1,bar: 2})// ...// 返回时将属性都转为 refreturn toRefs(state)
}
xml复制代码<script setup>
import { useFeature } from './feature.js'
// 可以解构而不会失去响应性
const { foo, bar } = useFeature()
</script>

toRefs 只会为源对象上已存在的属性创建 ref。如果要为还不存在的属性创建 ref,就要用到上面提到的 toRef

总结

reactive仅对对象类型有效(对象、数组和 MapSet 这样的集合类型),而对 stringnumberboolean 这样的原始类型无效。因为 Vue 的响应式系统是通过属性访问进行追踪的,如果我们直接“替换”一个响应式对象,这会导致对初始引用的响应性连接丢失。

ref对类型没有限制,其中的value和响应式对象的属性类似,ref 的 value 属性也是响应式的。同时,当值为对象类型时,Vue 会自动使用 reactive() 处理这个值,一个包含对象的 ref 可以响应式地替换整个对象

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 商家推广怎么利用C#发送视频短信
  • 如何限制docker使用的cpu,内存,存储
  • CSS选择器的魔法:探索:not-child()与:nth-child()
  • Vue3 reactive和ref
  • RateLimiter超时
  • 自建远程桌面RustDesk服务器(CentOS配置,保姆级案例)
  • 1999-2023年上市公司年报文本数据(PDF+TXT)
  • python用波形显示udp数据实现一个模拟示波器
  • 在Ubuntu 16.04上安装MySQL的方法
  • MFC工控项目实例之九选择下拉菜单主界面文本框显示菜单名
  • Java并发编程 第四章 共享模型之管程 上
  • 数据仓库系列14:数据清洗和转换的常见方法有哪些?
  • 从地图信息实时检测路口的各向通行状况、红绿灯及溢出情况
  • 初识C++|list类的使用及模拟实现
  • Qt 调用MFC dll,动态库中有界面
  • “寒冬”下的金三银四跳槽季来了,帮你客观分析一下局面
  • 4. 路由到控制器 - Laravel从零开始教程
  • ES6--对象的扩展
  • IDEA 插件开发入门教程
  • javascript 哈希表
  • js中的正则表达式入门
  • leetcode98. Validate Binary Search Tree
  • ng6--错误信息小结(持续更新)
  • Nodejs和JavaWeb协助开发
  • puppeteer stop redirect 的正确姿势及 net::ERR_FAILED 的解决
  • React-生命周期杂记
  • Vim Clutch | 面向脚踏板编程……
  • 爬虫进阶 -- 神级程序员:让你的爬虫就像人类的用户行为!
  • 悄悄地说一个bug
  • 使用API自动生成工具优化前端工作流
  • 我的zsh配置, 2019最新方案
  • 无服务器化是企业 IT 架构的未来吗?
  • 写给高年级小学生看的《Bash 指南》
  • 一加3T解锁OEM、刷入TWRP、第三方ROM以及ROOT
  • 智能网联汽车信息安全
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • 【干货分享】dos命令大全
  • k8s使用glusterfs实现动态持久化存储
  • ​LeetCode解法汇总2696. 删除子串后的字符串最小长度
  • ​zookeeper集群配置与启动
  • ​决定德拉瓦州地区版图的关键历史事件
  • #知识分享#笔记#学习方法
  • (02)Cartographer源码无死角解析-(03) 新数据运行与地图保存、加载地图启动仅定位模式
  • (2024.6.23)最新版MAVEN的安装和配置教程(超详细)
  • (C语言)输入自定义个数的整数,打印出最大值和最小值
  • (day6) 319. 灯泡开关
  • (二十九)STL map容器(映射)与STL pair容器(值对)
  • (教学思路 C#之类三)方法参数类型(ref、out、parmas)
  • (蓝桥杯每日一题)平方末尾及补充(常用的字符串函数功能)
  • (四)Tiki-taka算法(TTA)求解无人机三维路径规划研究(MATLAB)
  • (一) storm的集群安装与配置
  • (游戏设计草稿) 《外卖员模拟器》 (3D 科幻 角色扮演 开放世界 AI VR)
  • (转)socket Aio demo
  • (转)大型网站的系统架构
  • (转载)跟我一起学习VIM - The Life Changing Editor