首页 > 其他分享 >使用Ref还是Reactive?

使用Ref还是Reactive?

时间:2023-04-24 21:34:11浏览次数:46  
标签:count const reactive Ref value Reactive state 使用 ref

我喜欢Vue 3的Composition API,它提供了两种方法来为Vue组件添加响应式状态:refreactive。当你使用ref时到处使用.value是很麻烦的,但当你用reactive创建的响应式对象进行重构时,也很容易丢失响应性。 在这篇文章中,我将阐释你如何来选择reactive以及ref

一句话总结:默认情况下使用ref,当你需要对变量分组时使用reactive

Vue3的响应式

在我解释refreactive之前,你应该了解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)
    },
  })
}

proxygetset方法通常被称为代理陷阱。

这里强烈建议阅读官方文档来查看有关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有两个限制:

第一个限制是,它只适用于对象类型,比如对象、数组和集合类型,如MapSet。它不适用于原始类型,比如stringnumberboolean

第二个限制是,从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扩展来自动地添加.valueref上。你可以在Volar: Auto Complete Refs设置中开启:

image.png

相应的JSON设置:

"volar.autoCompleteRefs": true

为了减少CPU的使用,这个功能默认是禁用的。

比较

让我们总结一下reactiveref之间的区别:

reactive ref

标签:count,const,reactive,Ref,value,Reactive,state,使用,ref
From: https://www.cnblogs.com/chuckQu/p/17350975.html

相关文章

  • git-git、gitee使用介绍
    1.git介绍和安装1.我们为什么要使用git?1.1帮助开发者合并开发的代码1.2如果出现冲突代码的合并,会提示后提交代码的开发者,让其解决冲突1.3代码版本的管理,比如要找到之前某个版本的代码做对比,那么需要找到之前某个版本的代码2.svn和git的区别:git是分布式的,有本地和远程两......
  • flux 使用方法
    InfluxQLSHOWTAGVALUESWITHKEY="host"Fluxfrom(bucket:"geth")|>range(start:v.timeRangeStart)|>keyValues(keyColumns:["host"])|>keep(columns:["host"])|>distinct(column:"host&quo......
  • 在mac上使用docker部署Mongo数据库
    拉取镜像打开网址https://hub.docker.com/,搜索mongo,https://hub.docker.com/_/mongo执行命令dockerpullmongo启动容器执行命令dockerimage,查看到mongo的tag是5.0.16启动命令dockerrun-dit--namemongo5-p27017:27017-v/Users/huidongma/data/mongodb:......
  • drf-jwt、simplejwt的使用
    1.接口文档#前后端分离 -我们做后端,写接口-前端做前端,根据接口写app,pc,小程序-作为后端来讲,我们很清楚,比如登录接口/api/v1/login/---->post---->username,password编码方式json----》返回的格式{code:100,msg:登录成功}-后端人员,接口写完,一......
  • Java中缓存区的基本使用
    前言缓存区是一种内存空间,在计算机程序中被广泛使用来优化I/O操作的效率。在文件I/O操作中,缓存区用于缓存将要写入磁盘或读取到内存中的数据。这样可减少对磁盘的访问次数,提高I/O操作的效率。本文将介绍缓存区的基本使用以及一些注意点,并提供一个实例来演示如何将一个jpg图片复制......
  • 使用tcpkill实用程序终止TCP连接
    Linux连接久久不能释放的现象不常见,但偶然也会发生。进程虽不复存在,但是客户端的连接咬定青山不放松,死活也不肯吐出连接,导致重启进程时因操作系统判断监听端口被占用而无法启动。常规手段已经束手无策,这时候不得不想办法杀连接。一、tcpkill介绍tcpkill是网络嗅探工具包dsniff其中......
  • 1 Go语言介绍、 2 Go开发环境搭建 、3 第一个helloworld 、4 变量命名规范 、5 变量的
    目录1Go语言介绍2Go开发环境搭建3第一个helloworld4变量命名规范5变量的定义和使用1Go语言介绍#Go语言介绍Go即Golang,是Google公司2009年11月正式对外公开的一门编程语言Go是【静态强类型】语言,是区别于解析型语言的编译型语言(静态:类型固定强类型:不同类型不允许直接......
  • 使用Python进行ETL数据处理
    ETL(Extract,Transform,Load)是一种广泛应用于数据处理和数据仓库建设的方法论,它主要用于从各种不同的数据源中提取数据,经过一系列的处理和转换,最终将数据导入到目标系统中。本文将介绍如何使用Python进行ETL数据处理的实战案例。一、数据来源本次实战案例的数据来源是一个包含销售......
  • 劲(很)霸(不)酷(好)炫(用)的NLP可视化包:Dodorio 使用指北
    朋友们,朋友们,事情是这样的。最近心血来潮,突然想起很久以前看过的一个NLP可视化包。它的效果是下面这个样子:在此之前,已经有一些文章从论文的角度对这个包进行了介绍,详情请见推荐一个可交互的Attention可视化工具!我的Transformer可解释性有救啦?当时我第一眼就被这个包的效果折......
  • 【c&c++】C++ 关于编译出现“undefined reference to `std::cout‘“的问题
    1、问题概述        在使用gcc编译c++代码时会出现undefinedreferenceto`std::cout',如编译如下代码:#include<iostream>usingnamespacestd;intmain(){cout<<"Helloworld!";return0;}然而,gcc下编译出现的问题是: 2、解决方法使用g++编译,g++......