前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >vue3实战-完全掌握ref、reactive

vue3实战-完全掌握ref、reactive

原创
作者头像
yyds2026
发布于 2022-11-01 04:52:09
发布于 2022-11-01 04:52:09
3.5K00
代码可运行
举报
文章被收录于专栏:前端开发面经前端开发面经
运行总次数:0
代码可运行

知道大家使用 Vue3 的时候有没有这样的疑惑,“ref、rective 都能创建一个响应式对象,我该如何选择?”,“为什么响应式对象解构之后就失去了响应式?应该如何处理?” 今天咱们就来全面盘点一下 ref、reactive,相信看完你一定会有不一样的收获,一起学起来吧!

reactive()

基本用法

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import { reactive } from 'vue'

const state = reactive({ count: 0 })

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

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

代码语言:html
AI代码解释
复制
<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> 中顶层的导入和变量声明可以在模板中直接使用。

代码语言:html
AI代码解释
复制
<script setup>
import { reactive } from 'vue'

const state = reactive({ count: 0 })
</script>

<template>
  <div>{{ state.count }}</div>
</template>

响应式代理 vs 原始对象

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const raw = {}
const proxy = reactive(raw)

console.log(proxy === raw) // false

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<script setup>
const state = { count: 0 }
function add() {
  state.count++
}
</script>

<template>
  <button @click="add">
    {{ state.count }} <!-- 当点击button时,始终显示为 0 -->
  </button>
</template>

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const raw = {}
const proxy1 = reactive(raw)
const proxy2 = reactive(raw)

console.log(proxy1 === proxy2) // true

console.log(reactive(proxy1) === proxy1) // true

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const raw = {}
const proxy = reactive({ nested: raw })
const nested = reactive(raw)

console.log(proxy.nested === nested) // true

shallowReactive()

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const state = shallowReactive({
  foo: 1,
  nested: {
    bar: 2
  }
})

// 状态自身的属性是响应式的
state.foo++

// 下层嵌套对象不是响应式的,不会按期望工作
state.nested.bar++

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

reactive() 的局限性

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

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

<script setup>

import { reactive } from 'vue'

let state = reactive({ count: 0 })

function change() { // 非响应式替换

代码语言:txt
AI代码解释
复制
state = reactive({ count: 1 })}

</script>

<template>

代码语言:txt
AI代码解释
复制
<button @click="change">
代码语言:txt
AI代码解释
复制
  {{ state }} <!-- 当点击button时,始终显示为 { "count": 0 } -->
代码语言:txt
AI代码解释
复制
</button>

</template>

代码语言:txt
复制
  1. 将响应式对象的属性赋值或解构至本地变量,或是将该属性传入一个函数时,会失去响应性:
代码语言:javascript
代码运行次数:0
运行
复制

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

代码语言:txt
AI代码解释
复制
// 不会影响 state
代码语言:txt
AI代码解释
复制
count++

}

callSomeFunction(state.count)

代码语言:txt
复制

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

ref()

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

基本用法

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import { ref } from 'vue'

const count = ref(0)

console.log(count) // { value: 0 }

count.value++
console.log(count.value) // 1

和响应式对象的属性类似,ref 的 value 属性也是响应式的。同时,当值为对象类型时,Vue 会自动使用 reactive() 处理这个值。参考vue实战视频讲解:进入学习

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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 从一般对象上解构属性或将属性传递给函数时,不会丢失响应性:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const state = {
  count: ref(0)
}
// 解构之后,和 state.count 依然保持响应性连接
const { count } = state
// 会影响 state
count.value++

// 该函数接收一个 ref, 和传入的值保持响应性连接
function callSomeFunction(count) {
  // 会影响 state
  count.value++
}
callSomeFunction(state.count)

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// mouse.js
export function useMouse() {
  const x = ref(0)
  const y = ref(0)

  // ...
  return { x, y }
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<script setup>
import { useMouse } from './mouse.js'
// 可以解构而不会失去响应性
const { x, y } = useMouse()
</script>

ref 的解包

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

ref 在模板中的解包

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

代码语言:html
AI代码解释
复制
<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <div>
    {{ count }} <!-- 无需 .value -->
  </div>
</template>

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

代码语言:html
AI代码解释
复制
<script setup>
import { ref } from 'vue'

const object = { foo: ref(1) }

</script>

<template>
  <div>
    {{ object.foo }} <!-- 无需 .value -->
  </div>
</template>

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const object = { foo: ref(1) }

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

代码语言:css
AI代码解释
复制
<div>{{ object.foo + 1 }}</div>

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const object = { foo: ref(1) }
const { foo } = object
代码语言:css
AI代码解释
复制
<div>{{ foo + 1 }}</div>

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

ref 在响应式对象中的解包

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const count = ref(0)
const state = reactive({ count })

console.log(state.count) // 0

state.count = 1
console.log(state.count) // 1

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const count = ref(0)
const state = shallowReactive({ count })

console.log(state.count) // { value: 0 } 而不是 0

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const count = ref(1)
const state = reactive({ count })

const otherCount = ref(2)
state.count = otherCount

console.log(state.count) // 2
// 此时 count 已经和 state.count 失去连接
console.log(count.value) // 1
ref 在数组和集合类型的解包

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 的值,反之亦然。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 传递给一个组合式函数时会很有用:

代码语言:html
AI代码解释
复制
<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 组合为一个普通对象的方法。这个普通对象的每个属性和源对象的属性保持同步。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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) // 2

stateAsRefs.foo.value++
console.log(state.foo) // 3

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// feature.js
export function useFeature() {
  const state = reactive({
    foo: 1,
    bar: 2
  })

  // ...
  // 返回时将属性都转为 ref
  return toRefs(state)
}
代码语言:html
AI代码解释
复制
<script setup>
import { useFeature } from './feature.js'
// 可以解构而不会失去响应性
const { foo, bar } = useFeature()
</script>

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

以上就是 ref、reactive 的详细用法,不知道你有没有新的收获。接下来,我们来探讨一下响应式原理。

响应式原理

Vue2 的限制

大家都知道 Vue2 中的响应式是采⽤ Object.defineProperty() , 通过 getter / setter 进行属性的拦截。这种方式对旧版本浏览器的支持更加友好,但它有众多缺点:

  • 初始化时只会对已存在的对象属性进行响应式处理。也是说新增或删除属性,Vue 是监听不到的。必须使用特殊的 API 处理。
  • 数组是通过覆盖原型对象上的7个⽅法进行实现。如果通过下标去修改数据,Vue 同样是无法感知的。也要使用特殊的 API 处理。
  • 无法处理像 MapSet 这样的集合类型。
  • 带有响应式状态的逻辑不方便复用。

Vue3 的响应式系统

针对上述情况,Vue3 的响应式系统横空出世了!Vue3 使用了 Proxy 来创建响应式对象,仅将 getter / setter 用于 ref ,完美的解决了上述几条限制。下面的代码可以说明它们是如何工作的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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)
    }
  })
}

function ref(value) {
  const refObject = {
    get value() {
      track(refObject, 'value')
      return value
    },
    set value(newValue) {
      value = newValue
      trigger(refObject, 'value')
    }
  }
  return refObject
}

不难看出,当将一个响应性对象的属性解构为一个局部变量时,响应性就会“断开连接”。因为对局部变量的访问不会触发 get / set 代理捕获。

我们回到响应式原理。在 track() 内部,我们会检查当前是否有正在运行的副作用。如果有,就会查找到存储了所有追踪了该属性的订阅者的 Set,然后将当前这个副作用作为新订阅者添加到该 Set 中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// activeEffect 会在一个副作用就要运行之前被设置
let activeEffect

function track(target, key) {
  if (activeEffect) {
    const effects = getSubscribersForProperty(target, key)
    effects.add(activeEffect)
  }
}

副作用订阅将被存储在一个全局的 WeakMap<target, Map<key, Set<effect>>> 数据结构中。如果在第一次追踪时没有找到对相应属性订阅的副作用集合,它将会在这里新建。这就是 getSubscribersForProperty() 函数所做的事。

trigger() 之中,我们会再次查找到该属性的所有订阅副作用。这一次我们全部执行它们:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function trigger(target, key) {
  const effects = getSubscribersForProperty(target, key)
  effects.forEach((effect) => effect())
}

这些副作用就是用来执行 diff 算法,从而更新页面的。

这就是响应式系统的大致原理,Vue3 还做了编译器的优化,diff 算法的优化等等。不得不佩服尤大大,把 Vue 的响应式系统又提升了一个台阶!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
vue3基础ref,reactive,toRef ,toRefs 使用和理解
在 Vue 3 中,ref、reactive、toRef 和 toRefs 是用于响应式数据管理的重要工具。理解它们的使用方式和区别对于有效地利用 Vue 3 的响应式系统至关重要。以下是对这些工具的详细解释和示例。
肥晨
2024/08/27
6110
vue3基础ref,reactive,toRef ,toRefs 使用和理解
尤雨溪说:为什么Vue3 中应该使用 Ref 而不是 Reactive?
每次有同学学习到 vue3 的时候,总会问我:“ref 和 reactive 我们应该用哪个呢?” 我告诉他:“我们应该使用 ref,而不是 reactive”。那么此时同学就会有疑惑:“为什么呢?ref 还需要 .value 处理,reactive 看起来会更加简单呢?”
winty
2024/01/29
1.3K0
尤雨溪说:为什么Vue3 中应该使用 Ref 而不是 Reactive?
Vue 3 refs 学习
接受一个值,返回一个响应式并且可变的 ref 对象。ref 对象具有指向值的单个属性的值。
公众号---人生代码
2020/12/15
7960
推荐:非常详细的vue3.0开发笔记(7k字)
当将当前的技术栈从Vue 2.0升级到Vue 3.0时,有许多值得考虑的理由。以下是10个升级到Vue 3.0的理由:
微芒不朽
2023/08/21
5290
推荐:非常详细的vue3.0开发笔记(7k字)
一篇文章上手Vue3中新增的API
作者:liulongbin http://www.liulongbin.top:8085/#/?id=_3-setup 1. 初始化项目 // ① npm i -g @vue/cli // ② vue
Nealyang
2020/10/26
1.1K0
初探 Vue 3.0 的组装式 API(一)
Vue3 已经更新到 RC9,正式发布在即,其中让人倍加关注的重大更新:组装式 API (Composition API) 到底是什么,相比 Vue2 又有什么优势呢?
贤羽
2022/06/09
4080
@vue/composition-api速成课(通俗易懂版)
Composition API 将是 Vue 3 的核心功能,它具有许多更改和性能改进。我们也可以在 Vue 2 中通过 npm 插件@vue/composition-api 使用它。本人重点将带你了解:
前端迷
2020/09/30
2.9K0
最全系列的vue3入门教程『图文并茂』
Vue 3 是一个流行的开源JavaScript框架,用于构建用户界面和单页面应用。它带来了许多新特性和改进,包括更好的性能、更小的打包大小、更好的TypeScript支持、全新的组合式 API,以及一些新的内置组件。
linwu
2023/07/27
5.1K0
最全系列的vue3入门教程『图文并茂』
vue3中的reactive、ref、toRef和toRefs
reactive用于创建响应式对象,它返回一个对象的响应式代理。即:它返回的对象以及其中嵌套的对象都会通过 Proxy 包裹;当响应式对象被访问时,触发getter方法;当响应式对象被修改时,触发setter方法。在使用响应式对象时,我们可以像普通对象一样访问和修改数据。
九仞山
2023/10/14
2.5K0
快速使用Vue3最新的15个常用API
之前我写了一篇博客介绍了Vue3的新特性,简单了解了一下Vue3都有哪些特色,并且在文末带大家稍微体验了一下Vue3中 Compsition API 的简单使用
若川
2020/12/15
3.4K0
快速使用Vue3最新的15个常用API
vue3.0新特性初体验(一)
“关注 前端开发社区 ,回复“ 1” 即可加入 前端技术交流群,回复 “ 2” 即可免费领取500G前端干货!
前端老道
2020/09/08
9090
vue3.0新特性初体验(一)
前端系列12集-全局API,组合式API,选项式API的使用
The setup() hook serves as the entry point for Composition API usage in components in the following cases: 在以下情况下, setup() 钩子用作组件中 Composition API 使用的入口点:
达达前端
2023/10/08
5930
通过实例,理解 Vue3 的响应式设计
响应式指的是变量(如:数组、字符串、数字、对象等)在其值或它引用的任何其他变量在声明后发生更改时更新的能力。
前端修罗场
2022/07/29
1.7K0
简单说一下vue3中的那些晦涩难懂的概念(ref、reactive、unref、isRef、toRef、toRefs、shallowRef、triggerRef、custormRef)
何处锦绣不灰堆
2023/12/13
3060
简单说一下vue3中的那些晦涩难懂的概念(ref、reactive、unref、isRef、toRef、toRefs、shallowRef、triggerRef、custormRef)
vue3简易入门剖析
,发音同 “veet”)是一种新型前端构建工具,能够显著提升前端开发体验。它主要由两部分组成:
张哥编程
2024/12/13
4200
vue3之Composition API详解
没有Composition API之前vue相关业务的代码需要配置到option的特定的区域,中小型项目是没有问题的,但是在大型项目中会导致后期的维护性比较复杂,同时代码可复用性不高。Vue3.x中的composition-api就是为了解决这个问题而生的
前端进阶之旅
2021/10/16
2.2K0
vue3之Composition API详解
超全的Vue3文档【Vue2迁移Vue3】
链接:https://juejin.cn/post/6858558735695937544#heading-153
小丑同学
2021/03/25
2.8K0
学会这几个API,vue3直接上手
vue2开发过项目的,想直接上手vue3很快,几个API熟悉了就够了,其它的特性在使用vue3的过程慢慢去了解。任何在熟悉了使用之后再去研究一些API的原理,慢慢就能把vue3掌握。
wade
2022/03/28
7080
Vue3 中有哪些值得深究的知识点?
众所周知,前端技术一直更新很快,这不 vue3 也问世这么久了,今天就来给大家分享下vue3中值得注意的知识点。喜欢的话建议收藏,点个关注!
玖柒的小窝
2021/12/07
1.1K0
Vue3 中有哪些值得深究的知识点?
vue3中的reactive和ref
1、仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的 原始类型 无效。
用户10562852
2023/06/09
4190
vue3中的reactive和ref
推荐阅读
相关推荐
vue3基础ref,reactive,toRef ,toRefs 使用和理解
更多 >
LV.0
这个人很懒,什么都没有留下~
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档