logo
Laucher

Laucher

Laucher

2023年 02月 22日

0

Vue 3 Composition API 中的响应式特性:ref() 和 reactive()。

在组合式 API 中,我们使用两个函数来定义状态,即 ref() 和 reactive()。在本文中,我们将探讨这两个函数,概述它们的独特之处,并学习何时应该使用它们。作为额外内容,我们还将了解如何解包 refs、监视它们的变化,并使用它们创建隐式引用。。

封面图

在单页面应用程序(SPA)中,响应性指的是应用程序根据底层数据的变化能够更新其用户界面的能力。响应性使我们能够编写更清晰的代码,避免了我们需要手动根据数据变化来更新界面。相反,界面会自动更新。

然而,JavaScript 本身并不具备这种能力,因此我们可以通过像 React、Vue 和 Angular 这样的库和框架来实现响应性。例如,在 Vue 中,我们可以创建一个称为状态(state)的数据集;Vue 然后使用一套观察者(observers)和监听器(watchers)系统来跟踪状态并更新我们的界面。

当 Vue 检测到状态变化时,它使用虚拟 DOM 来重新渲染界面,确保界面始终保持最新状态,通过使用记忆化(memoization)和选择性更新等技术,使该过程尽可能快速和高效。有几种不同的方式可以定义我们希望 Vue 跟踪的应用程序状态。例如,在 Vue 3 的组合式 API 出现之前,我们使用一个返回所需跟踪对象的数据函数。

Vue reactive()

我们可以使用 reactive() 在 Vue 的组合式 API 中声明对象或数组的状态:

import { reactive } from 'vue'

const state = reactive({
  first_name: "laucher",
  last_name: "fasss",
})

在上面的代码片段中,我们使用 reactive() 函数告诉 Vue 我们希望跟踪这个对象的状态。然后,我们可以创建一个依赖于这个响应式对象的用户界面,Vue 将会跟踪状态对象并使用它来更新界面:

<template>
  <div>{{state.first_name}} {{state.last_name}}</div>
</template>
<script>
import { reactive } from 'vue'
export default {
  setup() {
    const state = reactive({
      first_name: "laucher",
      last_name: "fasss",
    })

    return { state }
  }
}
</script>

在上面的代码中,我们展示了响应式数据在我们的状态中。现在,Vue 的响应性系统可以跟踪该数据,并在其更改时更新界面。我们可以通过创建一个函数,在点击按钮时更改 first_name 和 last_name 来测试这一点:

<template>
  <div>{{state.first_name}} {{state.last_name}}</div>
  <button @click="swapNames">换名字</button>
</template>
<script>
import { reactive } from 'vue'
export default {
  setup() {
    const state = reactive({
      first_name: "laucher",
      last_name: "fasss",
    })

    const swapNames = () => {
      state.first_name = "kangkang"
      state.last_name = "jian"
    }

    return { state, swapNames }
  }
}
</script>

当点击按钮时,swapNames 函数会更改 first_name 和 last_name,而 Vue 会立即更新 UI 内容。

reactive() 函数是在 Vue 组件中创建响应式对象的强大工具,但由于其在内部工作,它有一个主要限制;它只能用于对象。因此,我们无法将其用于诸如字符串或数字等基本数据类型。为了处理这个限制,Vue 在应用程序中提供了第二个用于声明响应式状态的函数,即 ref()。

Vue ref()

ref() 函数可以保存任何值类型,包括基本数据类型和对象。因此,我们可以以类似的方式使用它,就像使用 reactive() 函数一样:

import { ref } from 'vue'

const age = ref(0)

ref() 函数返回一个特殊的响应式对象。要访问 ref() 跟踪的值,我们可以访问返回对象的 value 属性:

import { ref } from 'vue'

const age = ref(0)

if(age.value < 18) {
  console.log("你还小,不能饮酒,酒让给哥哥喝。")
} else {
  console.log("喝点啤酒吧,伙计!")
}

下面的代码展示了如何创建一个能够对 age ref() 的变化作出反应的用户界面:

<template>
  <h1>{{age}}</h1>
  <button @click="increaseAge">+ Increase age</button>
  <button @click="decreaseAge">- Decrease age</button>
</template>
<script>
import { ref } from 'vue'
export default {
  setup() {
    const age = ref(0);

    const increaseAge = () => {
      age.value++
    }

    const decreaseAge = () => {
      age.value--
    }

    return { age, increaseAge, decreaseAge }
  }
}
</script>

您可能注意到,在模板中使用 age ref() 时,我们无需指定值。当我们在模板中调用它时,Vue 会自动应用 unref(),因此我们在模板中不需要使用 value。

我们可以使用 ref 重新编写第一个 reactive() 属性的示例;唯一的区别是,在 JavaScript 代码中访问值时需要使用 .value 属性。

ref() 和 reactive(),你应该使用哪个?

ref() 和 reactive() 之间的显著区别在于,ref() 函数允许我们为基本类型和对象声明响应式状态,而 reactive() 仅为对象声明响应式状态。

换种说法则是:

  1. ref():当你想创建一个单独的响应式基本类型或对象属性时,最适合使用这个函数。它将单个值包装在一个响应式对象中,并提供了 .value 属性来访问包装后的值。在处理字符串、数字、布尔值等变量,或者对象内的个别属性时,可以使用 ref()。

  2. reactive():当你需要创建一个包含多个属性的响应式对象时,可以使用这个函数。它以一个对象作为参数,并使对象的所有属性都成为响应式的。你可以直接访问属性,无需使用 .value。

因此,在实际场景中,你会发现使用 ref() 的 Vue 代码比使用 reactive() 的更多,但 reactive() 很棒,因为它可以轻松接受在组合式 API 之前定义的状态。两者都能有效地跟踪响应性,因此使用哪个取决于个人偏好和编码风格。

响应性:Vue 2 与 Vue 3 的区别

Vue 3 并没有完全消除在 Vue 2 中定义响应式状态的默认方式;相反,Vue 3 提供了两个 API,即组合式 API 和选项 API。选项 API 主要以 Vue 2 的方式工作:

<template>
  <h1>{{age}}</h1>
  <button @click="increaseAge">+ Increase age</button>
  <button @click="decreaseAge">- Decrease age</button>
</template>
<script>
import { ref } from 'vue'
export default {
  data: function() {
    return {
      age: 0
    }
  },
  methods: {
    increaseAge() {
      this.age++
    },
    decreaseAge() {
      this.age--
    }
  }
}
</script>

在上面的示例中,我们在状态中声明了 age,并将其设置为零。通过这些方法,我们可以增加或减少年龄,然后在每次按钮点击时在视图上看到数据的变化。上面的代码使用了选项 API,并且对 Vue 2 和 3 都有效。

从 Vue 2 返回的数据属性与我们之前讨论过的组合式 API 中的 reactive() 函数非常相似。它只能作为对象工作;不能从数据属性包含的函数中返回像字符串这样的基本值。

迁移 Vue 2 应用程序以使用 ref() 和 reactive()

从 Vue 2 的选项 API 迁移到 Vue 3 的组合式 API 是相当直接的。我们可以很容易地将 Vue 2 中的状态转换为 ref() 或 reactive()。请看下面的 Vue 2 组件示例:

<template>
  <div>
    <button>Clicked {{xTimes}}</button>
  </div>
</template>
<script>
export default {
  data: function() {
    return {
      xTimes: 0  
    }
  },
  methods: {
    increase() {
      this.xTimes++
    }    
  }
}
</script> 

我们可以很容易地重写这个组件,以使用 ref() 来展示。请考虑下面的代码片段:

<template>
  <div>
    <button>Clicked {{xTimes}}</button>
  </div>
</template>
<script>
import {ref} from "vue"
export default {
  setup() {
    const xTimes = ref(0)
    return {xTimes}
  },
  methods: {
    increase() {
      this.xTimes++
    }    
  }
}
</script> 

我们也可以利用setup语法糖重写这个组件,以使用 reactive() 来展示。以下是重写后的代码片段:

<template>
  <div>
    <button>Clicked {{xTimes}}</button>
  </div>
</template>
<script setup>
import {ref} from "vue"
const xTimes = ref(0)
const increase = () => {
  xTimes++
}   

</script>

同时,我们可以使用 setup() 语法来使这段代码更加流畅,代码看起来会更像以下示例:

<template>
  <div>
    <p>Age: {{ state.age }}</p>
    <button @click="incrementAge">增加年龄</button>
  </div>
</template>

<script>
import { reactive, toRefs } from 'vue';

export default {
  setup() {
    const state = reactive({
      age: 0
    });

    const incrementAge = () => {
      state.age++;
    };

    return {
      ...toRefs(state),
      incrementAge
    };
  }
};
</script>

同时,我们可以使用 setup() 语法来使这段代码更加流畅,代码看起来会更像以下示例:

<template>
  <div>
    <button> 点击 {{xTimes}}</button>
  </div>
</template>
<script>
import {reactive} from "vue"

  setup() {
    const state = reactive({
      xTimes: 0
    })
    return {state}
  },
  methods: {
    increase() {
      this.state.xTimes++
    }    
  }
</script>

ref() 和 reactive() 是 Vue 3 组合式 API 中用于处理响应性的强大工具,但它们也带有一些需要注意的缺点:

1. 嵌套属性: ref() 的一个限制是,当您在 ref 对象中访问嵌套属性时,您需要使用.value属性。这可能导致代码不够直观,并使代码库变得更难阅读和维护。而在 reactive() 中,这不是一个问题,因为您可以直接访问嵌套属性。

2. 可变性: 尽管 ref() 和 reactive() 都提供了响应性,但它们在处理可变性时有所不同。ref() 将原始值和对象包装在一个方式中,使其具有可变性,因此对 ref 值的更改直接改变值本身。另一方面,reactive() 在整个对象周围创建代理,因此属性的更改是响应式的,但更改整个对象引用不会触发响应性。

3. 性能: ref() 和 reactive() 都会带来一些小的性能开销。

混合使用 ref() 和 reactive(): 是一个好主意吗?

在混合使用 ref() 和 reactive() 方面,并没有固定的规则或约定。是否这样做是主观的,取决于开发者的偏好和编码模式。

有一些库,比如 Vuelidate,使用 reactive() 来设置用于验证的状态。在这种情况下,将多个 ref() 函数与 reactive() 函数结合使用来设置验证规则可能是有意义的。

因此,您应该与团队达成共识,以避免在编写代码时产生混淆。ref() 和 reactive() 是声明状态非常高效的工具,它们可以一起使用而不会产生任何技术上的缺陷。

ref() 解封

Vue 通过在特定情况下自动解封 ref() 函数。在这些情况下使用 ref(),我们不需要使用.value,因为 Vue 会在解封时自动为我们执行。

Vue 自动解封 ref() 函数的一个场景是模板中。在模板中访问 ref() 时,我们可以获取 ref() 的值而不需要使用 .value,如下所示和上面的示例中所示:

<template>
  <div>
    <button @click="increase"> 点击 {{xTimes}}</button>
  </div>
</template>
<script>
import {ref} from "vue"
export default {
  setup() {
    const xTimes = ref(0)
    return {xTimes}
  },
  methods: {
    increase() {
      this.xTimes++
    }    
  }
}
</script>

当将 ref() 设置为 reactive() 对象的值时,当我们访问它时,Vue 也会自动解封 ref(),如下所示:

<template>
  <div>
    <button @click="increase"> 点击 {{ state.count }}</button>
  </div>
</template>
<script setup>
import { ref, reactive } from "vue";

const state = reactive({
  count: ref(0),
});

function increase() {
  state.count++;
}
</script>

使用 watch() 函数监视 ref() 的变化

我们可以使用 watch 函数来监视 ref() 或 reactive() 的变化。watch 函数允许我们定义一个回调,每当被监视的响应式值发生变化时,该回调就会被触发。它将上一个值和当前值作为参数传递给回调函数,从而允许我们根据变化的值执行一些操作。

这个函数在我们需要对 ref 进行更改做出反应并相应地更新应用程序的其他部分时特别有用,比如触发副作用、更新 UI 元素或进行 API 调用:

<template>
 <div>
  <button @click="increase"> 点击 {{ count }}</button>
 </div>
</template>
<script setup>
import { ref, watch } from "vue";

const count = ref(0),

watch(count, (newValue, oldValue) => {
 console.log(`The state changed from ${oldValue} to ${newValue}`)
});

function increase() {
 count.value += 1;
}
</script>

在上面的代码片段中,响应式值作为第一个参数提供给了 watch 函数,回调函数提供了新值和旧值供处理。

需要注意的是,如果属性的值不是响应式的,watch() 函数可能无法按预期工作。例如,要监视上面示例中定义的 count 的值,watch() 函数也可以将回调函数作为其第一个参数,如下所示的代码片段:

<template>
 <div>
  <button @click="increase"> 点击 {{ state.count }}</button>
 </div>
</template>
<script setup>
import { ref, reactive, watch } from "vue";

const state = reactive({
 count: 0,
});

function increase() {
 state.count += 1;
}

watch(() => state.count, (newValue, oldValue) => {
 console.log(`The state changed from ${oldValue} to ${newValue}`)
});
</script>

模板引用:创建隐式引用

在 Vue 中,ref 也可以用于标识组件或 DOM。当 ref 被这样使用时,它被称为模板引用(template ref)。模板引用在我们想要定位 Vue 组件的底层 DOM 元素时非常有用:

<template>
 <Child ref="child" />
</template>

<script setup>
import { ref, onMounted } from 'vue'
import Child from './Child.vue'

const child = ref(null)

onMounted(() => {
 console.log(child.value)
 console.log(child.value.$el)
})
</script>

在上面的代码片段中,Vue 组件使用 ref 来定位模板中定义的子组件。然后,ref 将保存组件的实例,可用于执行 DOM 操作。

模板引用允许我们在组件模板中访问子组件或元素,而无需为它们分配 ref 属性。模板引用通过根据模板中唯一标识符自动创建对子组件或元素的隐式引用来工作,从而简化了访问子组件的过程。

在处理动态或嵌套结构时,这特别有用。Vue 将引用分配为父组件实例的属性,使其在组件的 JavaScript 代码中可以访问。

需要记住的重要一点是,隐式引用仅适用于模板中的直接子组件或元素。

总结

应用响应性可以帮助您编写更简洁的代码。在本文中,我们探讨了如何使用 reactive() 和 ref() 函数来处理 Vue 中的响应性。此外,我们还研究了这些函数与 Vue 2 的选项 API 以及 Vue 3 的组合式 API 的关系,并讨论了何时使用每种方式。

  • ref() - 函数用于将普通 JavaScript 数据转换为响应式数据。它对于简单的状态管理非常有用,但需要使用 .value 属性来访问其值,以及需要处理可变性。
  • reactive() - 函数用于将整个对象转换为响应式对象。它适用于复杂的数据结构,可以直接访问和修改对象的属性,但不需要解封。
  • watch() - 函数允许我们监视响应式数据的变化,并在值发生变化时执行回调。这在处理副作用、更新界面元素或进行其他操作时非常有用。
  • 模板引用(template refs)使我们可以在模板中隐式地引用 DOM 元素或组件实例。这对于直接操作元素或组件非常方便。

希望这篇文章对您有所帮助,如果您有任何问题,请务必留下评论或联系我。谢谢!

评论