Vue 3中的Ref() 与Reactive() - 什么是正确的选择?

评论 5 浏览 0 2022-01-29

Composition API有两种方法可以将反应状态引入组件中。因此,你将面临是否使用ref、reactive或两者的决定。我将帮助你做出正确的选择,但让我们先快速介绍一下这两种方式。

快速介绍

Ref()reactive()是用来跟踪其参数的变化。当使用它们来初始化变量时,你给Vue的信息是:“嘿,我希望你在这些变量发生变化时,重新构建或重新评估依赖于这些变量的一切”。

在下面的示例中,personRefpersonReactive 都将在单击按钮后 更改其名称,但是person 只是一个普通的 JS 对象不会。

<template>
  {{ person.name }} <!-- will NOT change to Amy -->
  {{ personRef.name }} <!-- will change to Amy -->
  {{ personReactive.name }} <!-- will change to Amy -->
  <button @click="changeName('Amy')" />
</template>

<script>
const App = {
  setup() {
    const person = {name: 'John'};
    const personRef = ref({name: 'John'});
    const personReactive = reactive({name: 'John'});
    const changeName = (name) => {
      person.name = name;
      personRef.value.name = name;
      personReactive.name = name;
    }
    return {
      changeName,
      person,
      personRef,
      personReactive,
    }
  }
}
</script>

基本上,它们被用来使你的组件响应性(对更改做出反应)。

差异

有三个主要的区别,你应该注意到。

  • ref()可以接受基本类型 作为参数(最常见的是:BooleanStringNumber)以及对象,而reactive()只能接受对象作为参数。
// INVALID
const x = reactive(true);

// VALID
const x = ref(true);

但是对于对象来说,两种语法都是有效的。

// VALID
const x = reactive({ name: 'John'});

// VALID
const x = ref({ name: 'John'});

  • ref()有一个.value的属性,你必须用它来获取它的内容,但用reactive()你可以直接访问它。
// VALID
const x = reactive({ name: 'John'});
x.name = 'Ammy';
// x -> { name: 'Ammy' }

// VALID
const x = ref({ name: 'John'});
x.value.name = 'Ammy';
// x.value -> { name: 'Ammy' }

仅供参考:引用在传递给模板时会被解包,这就是为什么你不需要在那里写 .value 的原因。
  • 使用ref()你可以替换一个对象的整个实例,但使用reactive()你就不能了。
// INVALID - changes of x are NOT recorded by Vue
let x = reactive({name: 'John'});
x = reactive({todo: true});

// VALID
const x = ref({name: 'John'});
x.value = {todo: true};

为什么它们都存在呢?

看起来reactive()只是ref()的一个有限版本,那么你是否应该考虑在你的代码中使用reactive()?为什么不总是使用ref()呢?

我深挖了一下,找到了Vue 3团队决定给我们使用reactive()的可能性的原因,而不是把它当作一个内部对象,对Vue程序员来说是不可用的。

而且…当reactive()击败ref()时,有一个用例。

转变视角

但在此之前,让我们看一下Vue.js的源代码,以便更好地理解反应性类之间的关系。这里你可以看到Vue.js 3中ref() 实现的部分内容。

class RefImpl<T> {
  private _value: T
  private _rawValue: T

  public dep?: Dep = undefined
  public readonly __v_isRef = true

  constructor(value: T, public readonly __v_isShallow: boolean) {
    this._rawValue = __v_isShallow ? value : toRaw(value)
    this._value = __v_isShallow ? value : toReactive(value)
  }

  get value() {
    trackRefValue(this)
    return this._value
  }

  set value(newVal) {
    newVal = this.__v_isShallow ? newVal : toRaw(newVal)
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = this.__v_isShallow ? newVal : toReactive(newVal)
      triggerRefValue(this, newVal)
    }
  }
}

#8行,有一个构造函数,我们每次写ref(x)时都会调用这个构造函数。在它里面,有趣的事情发生了。在#10行中,toReactive(x)被调用,它是…只是一个函数,将一个对象改为reactive()!

结论ref()是在使用reactive(),你可以把ref()看作(几乎):

const myRef = reactive({
  value: "I'm ref!"
})

myRef.value // "I'm ref!"
myRef.value = "Changed"
myRef.value // "Changed"

什么时候reactive()比较好?

使用reactive()的一个理由是为了避免模板。在使用 refs 时,你可能会因为在你的代码中到处写 .value 而感到厌烦。然而,如果你需要基本类型,你将无法逃避ref(),除非你把它们放在一个对象中!

事实证明,当你想在一个变量中拥有一个完整的状态时,reactive()很方便,就像在options API中做的那样(在data())。原因是reactive()是单独追踪每个属性的,而不是引用。

这意味着,当Vue试图找出是否应该更新依赖于它的东西时,reactive()中的每个属性都被当作独立的ref()

如果你想使用ref()作为一个状态容器,每当一个属性更新时,任何使用该状态的地方都会被更新。这将引发不必要的重读,并拖慢应用程序。

使用一个状态变量的例子。

const state = reactive({
  person: {name: 'John'},
  isLoading: false,
  updated: true,
  ...
})

结论:你可以把reactive()当作一个未被包裹的refs()的容器,如果你想在一个组件内有一个单状态的变量,就可以使用它。

最后的说明

Ref()reactive()在开始时可能看起来非常相似,但它们的目的却有些不同。在这个问题上没有好的编程实践,所以这完全取决于你和你的团队如何决定。

在我看来,你应该盲目地选择ref()而不是reactive()。代码会更加一致,你也不需要考虑使用哪一个— 编码会稍微快一点。用.value访问变量可能会激怒你,但我认为那是一个变量是反应性的指标。用reactive()我们就失去了这个信息,而且有必要检查变量定义(或内存)。

但请记住关于特殊的使用情况,使reactive()成为你脑海中的好函数。如果你喜欢在一个组件内为一个状态设置一个变量,那么它恰恰是适合你的工具。

最后更新2022-11-12
5 个评论
#1 Jeannot Muller 2022-04-24

很好的解释。在我的清单上有一段时间要深入研究这个话题,但我总是太忙或太懒,只是用ref() ,但不知道别人用reactive()。现在我确实明白了其中的区别。谢谢你的分享。

Bartosz Salwiczek 2022-04-24

谢谢你!很高兴你发现它很有用

#2 leactz 2022-06-02

简洁而详细的解释;感谢你包括Vue.js源代码中的片段。

#3 Azel Alyne Tan 2022-09-16

谢谢你!对于像我这样的初学者来说,这很容易理解。

#4 MagicHacker 2022-09-06

解释得很好!我也是。

#5 Andy C 2022-09-21

不错的总结。不过,有一个问题。你说"使用ref(),你可以替换一个对象的整个实例,但在使用reactive()时,你不能:"你的代码并没有真正证明这一点;它所证明的是你试图覆盖一个常量。这将会失败,但不是因为Vue的原因--试图覆盖一个常量显然是无效的JS。

Bartosz Salwiczek 2022-11-03

谢谢你指出这一点!我会改正的。

标签