Vue学习之响应式基础

声明式响应状态

在组合式API中,推荐使用ref()函数来声明响应式状态:

Import{
   ref} from 'vue'

ref() 接收参数,并将其包裹在一个带有 .value 属性的 ref 对象中返回:

Const count=ref(0)
Console.log(count)//{value:0}
Console.log(count.value) //0

为什么要使用 ref?​

你可能会好奇:为什么我们需要使用带有 .value 的 ref,而不是普通的变量?为了解释这一点,我们需要简单地讨论一下 Vue 的响应式系统是如何工作的。

当你在模板中使用了一个 ref,然后改变了这个 ref 的值时,Vue 会自动检测到这个变化,并且相应地更新 DOM。这是通过一个基于依赖追踪的响应式系统实现的。当一个组件首次渲染时,Vue 会追踪在渲染过程中使用的每一个 ref。然后,当一个 ref 被修改时,它会触发追踪它的组件的一次重新渲染。

在标准的 JavaScript 中,检测普通变量的访问或修改是行不通的。然而,我们可以通过 getter 和 setter 方法来拦截对象属性的 get 和 set 操作。

该 .value 属性给予了 Vue 一个机会来检测 ref 何时被访问或修改。在其内部,Vue 在它的 getter 中执行追踪,在它的 setter 中执行触发。从概念上讲,你可以将 ref 看作是一个像这样的对象:
js

// 伪代码,不是真正的实现const myRef = {  _value: 0, 
 get value() {
      
  track()    
   return this._value  }, 
 set value(newValue) {
    
  this._value = newValue   
   trigger()  }
   }

另一个 ref 的好处是,与普通变量不同,你可以将 ref 传递给函数,同时保留对最新值和响应式连接的访问。当将复杂的逻辑重构为可重用的代码时,这将非常有用。
该响应性系统在深入响应式原理章节中有更详细的讨论。

深层响应性​

Ref 可以持有任何类型的值,包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构,比如 Map。
Ref 会使它的值具有深层响应性。这意味着即使改变嵌套对象或数组时,变化也会被检测到:
js

import {
    ref } from 'vue'
const obj = ref({
     nested: {
    count: 0 },  arr: ['foo', 'bar']})
function mutateDeeply() {
     // 以下都会按照期望工作 
  obj.value.nested.count++ 
  obj.value.arr.push('baz')
  }

非原始值将通过 reactive() 转换为响应式代理,该函数将在后面讨论。
也可以通过 shallow ref 来放弃深层响应性。对于浅层 ref,只有 .value 的访问会被追踪。浅层 ref 可以用于避免对大型数据的响应性开销来优化性能、或者有外部库管理其内部状态的情况。
阅读更多:
• 减少大型不可变数据的响应性开销
• 与外部状态系统集成

DOM 更新时机​

当你修改了响应式状态时,DOM 会被自动更新。但是需要注意的是,DOM 更新不是同步的。Vue 会在“next tick”更新周期中缓冲所有状态的修改,以确保不管你进行了多少次状态修改,每个组件都只会被更新一次。
要等待 DOM 更新完成后再执行额外的代码,可以使用 nextTick() 全局 API:
js

import {
    nextTick } from 'vue'async 
function increment() {
    
 count.value++ 
  await 
  nextTick()  // 现在 DOM 已经更新了
  }
reactive()

还有另一种声明响应式状态的方式,即使用 reactive() API。与将内部值包装在特殊对象中的 ref 不同,reactive() 将使对象本身具有响应性:
js

import {
    reactive } from 'vue'
const state = reactive({
    count: 0 })
参考:为 reactive() 标注类型 

在模板中使用:
template

<button @click="state.count++">  {
   {
    state.count }}</button>

响应式对象是 JavaScript 代理,其行为就和普通对象一样。不同的是,Vue 能够拦截对响应式对象所有属性的访问和修改,以便进行依赖追踪和触发更新。
reactive() 将深层地转换对象:当访问嵌套对象时,它们也会被 reactive() 包装。当 ref 的值是一个对象时,ref() 也会在内部调用它。与浅层 ref 类似,这里也有一个 shallowReactive() API 可以选择退出深层响应性。

Reactive Proxy vs. Original​

值得注意的是,reactive() 返回的是一个原始对象的 Proxy,它和原始对象是不相等的:
js

const raw = {
   }
const proxy = reactive(raw)// 代理对象和原始对象不是全等的
console.log(proxy === raw) // false

只有代理对象是响应式的,更改原始对象不会触发更新。因此,使用 Vue 的响应式系统的最佳实践是 仅使用你声明对象的代理版本。
为保证访问代理的一致性,对同一个原始对象调用 reactive() 会总是返回同样的代理对象,而对一个已存在的代理对象调用 reactive() 会返回其本身:
js

// 在同一个对象上调用 reactive() 会返回相同的代理
console.log(reactive(raw) === proxy) // true
// 在一个代理上调用 reactive() 会返回它自己
console.log(reactive(proxy) === proxy) // true

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

const proxy = reactive({
   })const raw = {
   }
proxy.nested = raw
console.log(proxy.nested === raw) // false
reactive() 的局限性​
reactive() API 有一些局限性:
  1. 有限的值类型:它只能用于对象类型 (对象、数组和如 Map、Set 这样的集合类型)。它不能持有如 string、number 或 boolean 这样的原始类型。
  2. 不能替换整个对象:由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。这意味着我们不能轻易地“替换”响应式对象,因为这样的话与第一个引用的响应性连接将丢失:
    js
    let state = reactive({ count: 0 })// 上面的 ({ count: 0 }) 引用将不再被追踪// (响应性连接已丢失!)state = reactive({ count: 1 })
  3. 对解构操作不友好:当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接:
    js
const state = reactive({
    count: 0 })// 当解构时,count 已经与 state.count 断开连接
let {
    count } = state// 不会影响原始的 
statecount++// 该函数接收到的是一个普通的数字并且无法追踪的变化
state.count // 我们必须传入整个对象以保持响应性
callSomeFunction(state.count)

由于这些限制,我们建议使用 ref() 作为声明响应式状态的主要 API。

额外的 ref 解包细节​

作为 reactive 对象的属性​
一个 ref 会在作为响应式对象的属性被访问或修改时自动解包。换句话说,它的行为就像一个普通的属性:
js

const count = ref(0)
const state = reactive({
     count})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1

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

const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
// 原始 ref 现在已经和 state.count 失去联系
console.log(count.value) // 1

只有当嵌套在一个深层响应式对象内时,才会发生 ref 解包。当其作为浅层响应式对象的属性被访问时不会解包。
数组和集合的注意事项​
与 reactive 对象不同的是,当 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)

在模板中解包的注意事项​

在模板渲染上下文中,只有顶级的 ref 属性才会被解包。
在下面的例子中,count 和 object 是顶级属性,但 object.id 不是:
js

const count = ref(0)const object = {
    id: ref(1) }
//因此,这个表达式按预期工作:
template
{
   {
    count + 1 }}
...但这个不会:
template
{
   {
    object.id + 1 }}

渲染的结果将是 [object Object]1,因为在计算表达式时 object.id 没有被解包,仍然是一个 ref 对象。为了解决这个问题,我们可以将 id 解构为一个顶级属性:
js

const {
    id } = object
<template>
{
   {
    id + 1 }}
</template>

现在渲染的结果将是 2。
另一个需要注意的点是,如果 ref 是文本插值的最终计算值 (即 { { }} 标签),那么它将被解包,因此以下内容将渲染为 1:

<template>
{
   {
    object.id }}
</template>

该特性仅仅是文本插值的一个便利特性,等价于 { { object.id.value }}

来自 https://cn.vuejs.org/guide/essentials/reactivity-fundamentals.html

相关推荐

  1. Vue学习响应基础

    2024-02-23 17:32:01       49 阅读
  2. vue3-响应基础ref

    2024-02-23 17:32:01       58 阅读
  3. vue响应基础

    2024-02-23 17:32:01       37 阅读
  4. Vue基础(2)响应基础

    2024-02-23 17:32:01       30 阅读
  5. 前端学习路(2) Vue3响应模式设计原理

    2024-02-23 17:32:01       34 阅读
  6. Vue3---基础4(响应数据) Vue2

    2024-02-23 17:32:01       34 阅读
  7. Vue3响应基础——ref()和reactive()

    2024-02-23 17:32:01       36 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-02-23 17:32:01       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-02-23 17:32:01       101 阅读
  3. 在Django里面运行非项目文件

    2024-02-23 17:32:01       82 阅读
  4. Python语言-面向对象

    2024-02-23 17:32:01       91 阅读

热门阅读

  1. PHP安全

    2024-02-23 17:32:01       54 阅读
  2. Redis实现去重任务队列

    2024-02-23 17:32:01       63 阅读
  3. SpringBoot事务传播机制

    2024-02-23 17:32:01       46 阅读
  4. ELEC362 C++ Department of Electrical Engineering and Electronics

    2024-02-23 17:32:01       42 阅读
  5. jquery简介与解析

    2024-02-23 17:32:01       44 阅读
  6. 回调函数(Language C)

    2024-02-23 17:32:01       50 阅读