使用Ref还是Reactive?
我喜欢Vue 3的Composition API,它提供了两种方法来为Vue组件添加响应式状态:ref和reactive。当你使用ref时到处使用.value是很麻烦的,但当你用reactive创建的响应式对象进行重构时,也很容易丢失响应性。 在这篇文章中,我将阐释你如何来选择reactive以及ref。
一句话总结:默认情况下使用
ref,当你需要对变量分组时使用reactive。
Vue3的响应式
在我解释ref和reactive之前,你应该了解Vue3响应式系统的基本知识。
如果你已经掌握了Vue3响应式系统是如何工作的,你可以跳过本小节。
很不幸,JavaScript默认情况下并不是响应式的。让我们看看下面代码示例:
let price = 10.0
const quantity = 2
const total = price * quantity
console.log(total) // 20
price = 20.0
console.log(total) // ️ total is still 20
在响应式系统中,我们期望每当price或者quantity改变时,total就会被更新。但是JavaScript通常情况下并不会像预期的这样生效。
你也许会嘀咕,为什么Vue需要响应式系统?答案很简单:Vue 组件的状态由响应式 JavaScript 对象组成。当你修改这些对象时,视图或者依赖的响应式对象就会更新。
因此,Vue框架必须实现另一种机制来跟踪局部变量的读和写,它是通过拦截对象属性的读写来实现的。这样一来,Vue就可以跟踪一个响应式对象的属性访问以及更改。
由于浏览器的限制,Vue 2专门使用getters/setters来拦截属性。Vue 3对响应式对象使用Proxy,对ref使用getters/setters。下面的伪代码展示了属性拦截的基本原理;它解释了核心概念,并忽略了许多细节和边缘情况:
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
track(target, key)
return target[key]
},
set(target, key, value) {
target[key] = value
trigger(target, key)
},
})
}
proxy的get和set方法通常被称为代理陷阱。
这里强烈建议阅读官方文档来查看有关Vue响应式系统的更多细节。
reactive()
现在,让我们来分析下,你如何使用Vue3的reactive()函数来声明一个响应式状态:
import { reactive } from 'vue'
const state = reactive({ count: 0 })
该状态默认是深度响应式的。如果你修改了嵌套的数组或对象,这些更改都会被vue检测到:
import { reactive } from 'vue'
const state = reactive({
count: 0,
nested: { count: 0 },
})
watch(state, () => console.log(state))
// "{ count: 0, nested: { count: 0 } }"
const incrementNestedCount = () => {
state.nested.count += 1
// Triggers watcher -> "{ count: 0, nested: { count: 1 } }"
}
限制
reactive()API有两个限制:
第一个限制是,它只适用于对象类型,比如对象、数组和集合类型,如Map和Set。它不适用于原始类型,比如string、number或boolean。
第二个限制是,从reactive()返回的代理对象与原始对象是不一样的。用===操作符进行比较会返回false:
const plainJsObject = {}
const proxy = reactive(plainJsObject)
// proxy is NOT equal to the original plain JS object.
console.log(proxy === plainJsObject) // false
你必须始终保持对响应式对象的相同引用,否则,Vue无法跟踪对象的属性。如果你试图将一个响应式对象的属性解构为局部变量,你可能会遇到这个问题:
const state = reactive({
count: 0,
})
// ️ count is now a local variable disconnected from state.count
let { count } = state
count += 1 // ️ Does not affect original state
幸运的是,你可以首先使用toRefs将对象的所有属性转换为响应式的,然后你可以解构对象而不丢失响应:
let state = reactive({
count: 0,
})
// count is a ref, maintaining reactivity
const { count } = toRefs(state)
如果你试图重新赋值reactive的值,也会发生类似的问题。如果你"替换"一个响应式对象,新的对象会覆盖对原始对象的引用,并且响应式连接会丢失:
const state = reactive({
count: 0,
})
watch(state, () => console.log(state), { deep: true })
// "{ count: 0 }"
// ️ The above reference ({ count: 0 }) is no longer being tracked (reactivity connection is lost!)
state = reactive({
count: 10,
})
// ️ The watcher doesn't fire
如果我们传递一个属性到函数中,响应式连接也会丢失:
const state = reactive({
count: 0,
})
const useFoo = (count) => {
// ️ Here count is a plain number and the useFoo composable
// cannot track changes to state.count
}
useFoo(state.count)
ref()
Vue提供了ref()函数来解决reactive()的限制。
ref()并不局限于对象类型,而是可以容纳任何值类型:
import { ref } from 'vue'
const count = ref(0)
const state = ref({ count: 0 })
为了读写通过ref()创建的响应式变量,你需要通过.value属性来访问:
const count = ref(0)
const state = ref({ count: 0 })
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
state.value.count = 1
console.log(state.value) // { count: 1 }
你可能会问自己,ref()如何能容纳原始类型,因为我们刚刚了解到Vue需要一个对象才能触发get/set代理陷阱。下面的伪代码展示了ref()背后的简化逻辑:
function ref(value) {
const refObject = {
get value() {
track(refObject, 'value')
return value
},
set value(newValue) {
value = newValue
trigger(refObject, 'value')
},
}
return refObject
}
当拥有对象类型时,ref自动用reactive()转换其.value:
ref({}) ~= ref(reactive({}))
如果你想深入了解,可以在源码中查看
ref()的实现。
不幸的是,也不能对用ref()创建的响应式对象进行解构。这也会导致响应式丢失:
import { ref } from 'vue'
const count = ref(0)
const countValue = count.value // ️ disconnects reactivity
const { value: countDestructured } = count // ️ disconnects reactivity
但是,如果将ref分组在一个普通的JavaScript对象中,就不会丢失响应式:
const state = {
count: ref(1),
name: ref('Michael'),
}
const { count, name } = state // still reactive
ref也可以被传递到函数中而不丢失响应式。
const state = {
count: ref(1),
name: ref('Michael'),
}
const useFoo = (count) => {
/**
* The function receives a ref
* It needs to access the value via .value but it
* will retain the reactivity connection
*/
}
useFoo(state.count)
这种能力相当重要,因为它在将逻辑提取到组合式函数中时经常被使用。 一个包含对象值的ref可以响应式地替换整个对象:
const state = {
count: 1,
name: 'Michael',
}
// Still reactive
state.value = {
count: 2,
name: 'Chris',
}
解包refs()
在使用ref时到处使用.value可能很麻烦,但我们可以使用一些辅助函数。
unref实用函数
unref()是一个便捷的实用函数,在你的值可能是一个ref的情况下特别有用。在一个非ref上调用.value会抛出一个运行时错误,unref()在这种情况下就很有用:
import { ref, unref } from 'vue'
const count = ref(0)
const unwrappedCount = unref(count)
// same as isRef(count) ? count.value : count`
如果unref()的参数是一个ref,就会返回其内部值。否则就返回参数本身。这是的val = isRef(val) ? val.value : val语法糖。
模板解包
当你在模板上调用ref时,Vue会自动使用unref()进行解包。这样,你永远不需要在模板中使用.value进行访问:
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<span>
<!-- no .value needed -->
{{ count }}
</span>
</template>
只在
ref是模板中的顶级属性时才生效。
侦听器
我们可以直接传递一个ref作为侦听器的依赖:
import { watch, ref } from 'vue'
const count = ref(0)
// Vue automatically unwraps this ref for us
watch(count, (newCount) => console.log(newCount))
Volar
如果你正在使用VS Code,你可以通过配置Volar扩展来自动地添加.value到ref上。你可以在Volar: Auto Complete Refs设置中开启:
相应的JSON设置:
"volar.autoCompleteRefs": true
为了减少CPU的使用,这个功能默认是禁用的。
比较
让我们总结一下reactive和ref之间的区别:
| reactive | ref |
|---|---|
| 只对对象类型起作用 | 对任何类型起作用 |
在<script>和<template>中访问值没有区别 |
访问<script>和<template>中的值的行为不同 |
| 重新赋值一个新的对象会"断开"响应式 | 对象引用可以被重新赋值 |
属性可以在没有.value的情况下被访问 |
需要使用.value来访问属性 |
| 引用可以通过函数进行传递 | |
| 解构的值不是响应式的 | |
| 与Vue2的data对象相似 |
我的观点
我最喜欢ref的地方是,如果你看到它的属性是通过.value访问的,你就知道它是一个响应式的值。如果你使用一个用reactive创建的对象,就不那么清楚了:
anyObject.property = 'new' // anyObject could be a plain JS object or a reactive object
anyRef.value = 'new' // likely a ref
这个假设只有在你对ref有基本的了解,并且知道你用.value来读取响应式变量时才有效。
如果你在使用ref,你应该尽量避免使用具有value属性的非响应式对象:
const dataFromApi = { value: 'abc', name: 'Test' }
const reactiveData = ref(dataFromApi)
const valueFromApi = reactiveData.value.value //
如果你刚开始使用Composition API,reactive可能更直观,如果你试图将一个组件从Options API迁移到Composition API,它是相当方便的。reactive的工作原理与data内的响应式属性非常相似:
<script>
export default {
data() {
count: 0,
name: 'MyCounter'
},
methods: {
increment() {
this.count += 1;
},
}
};
</script>
你可以简单地将data中的所有内容复制到reactive中,然后将这个组件迁移到Composition API中:
<script setup>
setup() {
// Equivalent to "data" in Options API
const state = reactive({
count: 0,
name: 'MyCounter'
});
const {count, name} = toRefs(statee)
// Equivalent to "methods" in Options API
increment(username) {
state.count += 1;
}
}
</script>
比较ref和reactive
一个推荐的模式是在一个reactive对象中对ref分组:
const loading = ref(true)
const error = ref(null)
const state = reactive({
loading,
error,
})
// You can watch the reactive object...
watchEffect(() => console.log(state.loading))
// ...and the ref directly
watch(loading, () => console.log('loading has changed'))
setTimeout(() => {
loading.value = false
// Triggers both watchers
}, 500)
如果你不需要state对象本身的响应式,你可以在一个普通的JavaScript对象中进行分组。 对 refs 进行分组的结果是一个单一的对象,它更容易处理,并使你的代码保持有序。你可以看到分组后的 refs 属于一起,并且是相关的。
这种模式也被用于像Vuelidate这样的库中,他们使用
reactive()来设置验证的状态。
总结起来,社区中的最佳实践是默认使用ref,在需要分组的时候使用reactive。
总结
那么,你究竟该使用ref还是reactive?
我的建议是默认使用ref,当你需要分组时使用reactive。Vue社区也有同样的观点,但如果你决定默认使用reactive,也完全没有问题。
ref和reactive都是在Vue 3中创建响应式变量的强大工具。你甚至可以在没有任何技术缺陷的情况下同时使用它们。只要你选择你喜欢的那一个,并尽量在写代码时保持一致就可以了!
以上就是本文的全部内容,如果对你有所帮助,欢迎点赞、收藏、转发~
使用Ref还是Reactive?的更多相关文章
- 认真总结Vue3中ref与reactive区别和isRef与isReactive 类型判断
1.什么是ref? 1.ref和reactive-样 也是用来实现响应式数据的方法 由于reactive必须传递一个对象, 所以导致在企业开发中如果我们只想让某个变量实现响应式的时候会非常麻烦 所以V ...
- # vue3 ref 和 reactive 函数
vue3 ref 和 reactive 函数 前言 上一篇博文介绍 setup 函数的时候,最后出现一个问题,就是在 setup 函数中,编写一个事件,直接去修改定义的变量,发现页面上没有更新成功,并 ...
- vue3 第二天vue响应式原理以及ref和reactive区别
前言: 前天我们学了 ref 和 reactive ,提到了响应式数据和 Proxy ,那我们今天就来了解一下,vue3 的响应式 在了解之前,先复习一下之前 vue2 的响应式原理 vue2 的响应 ...
- vue3响应式原理以及ref和reactive区别还有vue2/3生命周期的对比,第二天
前言: 前天我们学了 ref 和 reactive ,提到了响应式数据和 Proxy ,那我们今天就来了解一下,vue3 的响应式 在了解之前,先复习一下之前 vue2 的响应式原理 vue2 的响应 ...
- 熬夜讲解vue3组合API中setup、 ref、reactive的用法
1.初识setUp的使用 简单介绍下面的代码功能: 使用ref函数,去使用监听某一个变量的变化,并且把它渲染到视图上. setUp函数是组合API的入口函数.这个是非常重要的. setUp可以去监听变 ...
- Vue3.0工程创建 && setup、ref、reactive函数 && Vue3.0响应式实现原理
1 # 一.创建Vue3.0工程 2 # 1.使用vue-cli创建 3 # 官方文档: https://cli.vuejs.org/zh/guide/creating-a-project.html# ...
- 第三十九篇:Vue3 watch(ref和reactive的监视)
好家伙, 1.vue2中的watch是调用配置项,(只能写一个) vue3中的watch是一个函数(可以写很多个) 2.watch一些用法: 这里是定义的数据 set up(){ let sum =r ...
- Vue3响应式系统api 之 ref reactive
reactive 接收一个普通对象然后返回该普调对象的响应式代理.等同于2.x的 Vue.observable() Vue3中响应数据核心是 reactive , reactive 中的实现是由 P ...
- 全面了解Vue3的 ref 和相关函数和计算属性
基础类型的响应性 -- ref 在vue3里面,我们可以通过 reactive 来实现引用类型的响应性,那么基础类型的响应性如何来实现呢? 可能你会想到这样来实现: const count = rea ...
- Vue3源码分析之 Ref 与 ReactiveEffect
Vue3中的响应式实现原理 完整 js版本简易源码 在最底部 ref 与 reactive 是Vue3中的两个定义响应式对象的API,其中reactive是通过 Proxy 来实现的,它返回对象的响应 ...
随机推荐
- NodeJS - XSS-href
参考:https://owasp-skf.gitbook.io/asvs-write-ups/cross-site-scripting-href-xss-href/kbid-3-xss-url 输入h ...
- 2.Vue模板语法
1.模板语法的概述 (1)如何理解前端渲染 将数据填充到HTML标签中,生成静态的HTML内容 2.前端渲染方式 (1)原生JS拼接字符串 (2)使用前端模板引擎 (3)使用Vue特有的模板语法 3. ...
- CentOS VMWare安装纪要
一.VMware虚拟机下载与安装 版本:VMware Workstation 16 Pro 二.CentOS下载与安装 版本:CentOS-7-x86_64-DVD-2009.iso 三.CentOS ...
- lc.59 螺旋矩阵 II
题目描述 给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix . 示例 输入:n = 3 输出:[[1,2,3],[8,9, ...
- AreEngine 求最小面积的外接矩形,非IEnvelope,表达不清楚了
1,总是会得到一些奇奇怪怪的要求,求一个面对象的外接最小面积的矩形,和ArcToolBox中的Mininum Bounding Geometry功能下的RECTANGLE_BY_AREA想似.具体看下 ...
- Mac 环境下 编译 spring 源码
环境:macos idea jdk1.8 首先,在 spring.io的 git 地址 上下载下来源码后,执行里面的 gradlew命令,一般只要网络没有问题,都是可以成功的 然后,看显示的 Welc ...
- mininet配置命令
Mininet实验手册 一.安装 1. 直接使用带有完整软件的VM(略) 2. 源码安装 1) 下载 git clone git://github.com/mininet/mininet 2) ...
- k8s namespace kubeDNS
图中kube-dns只是一个service,但是他对外提供k8s集群内部的dns服务,真正的dns server,是 coredns这几个pod k8s namespace 的作用只是提供逻辑上的组件 ...
- Qt 学习笔记 - 第五章 - Qt 时间编程 - Qt 时钟
原文地址:Qt 学习笔记 - 第五章 - Qt 时间编程 - Qt 时钟 Qt 学习笔记全系列传送门: Qt 学习笔记 - 第一章 - 快速开始.信号与槽 Qt 学习笔记 - 第二章 - 添加图片.布 ...
- 坚叔:让科幻片的概念变成产品丨编程挑战赛 x 嘉宾分享
前言 本文基于资深创业者@坚叔在「RTE 2022 创新编程挑战赛」宣讲活动中分享内容二次整理. 嘉宾简介:陈坚(坚叔),国内二次元 AR/VR 资深创业者,国内第一批空间虚拟数字化从业人员,获得政府 ...