Install ExtensionApp Store

Reactivity In Vue — Smashing Magazine

About The Author关于作者

Front-end developer based in Lagos, Nigeria. He enjoys converting designs into code and building things for the web. More about Timi ↬
前端开发员,常驻尼日利亚拉各斯。他喜欢将设计转化为代码,并为网络构建东西。 关于 Timi ↬ 的更多信息

Email Newsletter电子邮件通讯

Weekly tips on front-end & UX.
关于前端和用户体验的每周提示。

Trusted by 200,000+ folks.受到 20 多万人的信赖。

Reactivity is the ability for a variable (array, string, number, object, etc) to update when its value or any other variable that it makes reference to is changed after declaration.
重现性是指变量(数组、字符串、数字、对象等)在声明后,当其值或其引用的其他变量发生变化时,能够进行更新。

In this article, we’re going to look at reactivity in Vue, how it works, and how we can create reactive variables using newly created methods and functions. This article is targeted at developers who have a good understanding of how Vue 2.x works and are looking to get familiar with the new Vue 3.
本文将介绍 Vue 中的反应性、其工作原理以及如何使用新创建的方法和函数创建反应性变量。本文的目标读者是对 Vue 2.x 的工作原理有一定了解,并希望熟悉新版 Vue 3.X 的开发人员。

We’re going to build a simple application to better understand this topic. The code for this app can be found on GitHub.
我们将构建一个简单的应用程序,以便更好地理解这一主题。此应用程序的代码可在 GitHub 上找到。

By default, JavaScript isn’t reactive. This means that if we create the variable boy and reference it in part A of our application, then proceed to modify boy in part B, part A will not update with the new value of boy.
默认情况下,JavaScript 不是反应式的。这意味着,如果我们在应用程序的 A 部分创建变量 boy 并引用它,然后在 B 部分修改 boy ,A 部分将不会更新 boy 的新值。

let framework = 'Vue';
let sentence = `${framework} is awesome`;
console.log(sentence)
 // logs "Vue is awesome"
framework = 'React';
console.log(sentence)
//should log "React is awesome" if 'sentence' is reactive.

The snippet above is a perfect example of the non-reactive nature of JavaScript — hence, why the change isn’t reflected in the sentence variable.
上面的代码段是 JavaScript 非反应性的一个完美例子,因此, sentence 变量中并没有反映出变化。

In Vue 2.x, props, computed, and data() were all reactive by default, with the exception of properties that are not present in data when such components are created. This means that when a component is injected into the DOM, only the existing properties in the component’s data object would cause the component to update if and when such properties change.
在 Vue 2.x 中, props 、 computed 和 data() 默认都是反应式的。和 data() 默认都是反应式的,但创建此类组件时 data 中不存在的属性除外。这意味着,当一个组件被注入 DOM 时,只有组件的 props 对象中的现有属性会在这些属性发生变化时导致组件更新。

Internally, Vue 3 uses the Proxy object (an ECMAScript 6 feature) to ensure that these properties are reactive, but it still provides the option to use Object.defineProperty from Vue 2 for Internet Explorer support (ECMAScript 5). This method defines a new property directly on an object, or modifies an existing property on an object, and returns the object.
在内部,Vue 3 使用 Proxy 对象(ECMAScript 6 的一个特性)来确保这些属性是反应性的,但它仍然提供了使用 Vue 2 中 Object.defineProperty 的选项,以支持 Internet Explorer(ECMAScript 5)。该方法可直接在对象上定义新属性,或修改对象上的现有属性,并返回对象。

At first glance and since most of us already know that reactivity is not new in Vue, it might seem unnecessary to make use of these properties, but the Options API has its limitations when you’re dealing with a large application with reusable functions in several parts of the application. To this end, the new Composition API was introduced to help with abstracting logic in order to make a code base easier to read and maintain. Also, we can now easily make any variable reactive regardless of its data type using any of the new properties and methods.
乍一看,由于我们大多数人已经知道反应性并不是 Vue 的新特性,因此使用这些属性似乎没有必要,但当您处理的是一个在多个部分都有可重用功能的大型应用程序时,Options API 有其局限性。为此,我们引入了新的 Composition API 来帮助抽象逻辑,使代码库更易于阅读和维护。此外,我们现在可以使用任何新的属性和方法,轻松使任何变量成为反应型变量,而不管其数据类型如何。

When we use the setup option, which serves as the entry point for the Composition API, the data object, computed properties, and methods are inaccessible because the component instance has not yet been created when setup is executed. This makes it impossible to take advantage of the built-in reactivity in any of these features in setup. In this tutorial, we’re going to learn about all of the ways we can do this.
当我们使用作为组件 API 入口点的 setup 选项时, data 对象、 computed 属性和 methods 都无法访问,因为在执行 setup 时组件实例尚未创建。这使得我们无法利用 setup 中这些功能的内置反应性。在本教程中,我们将学习所有可以做到这一点的方法。

The Reactive Method #反应式方法 #

According to the documentation, the reactive method, which is the equivalent of Vue.observable() in Vue 2.6, can be useful when we’re trying to create an object all of whose properties are reactive (such as the data object in the Options API). Under the hood, the data object in the Options API uses this method to make all of the properties in it reactive.
根据文档介绍, reactive 方法相当于 Vue 2.6 中的 Vue.observable() ,当我们试图创建一个所有属性都是反应式的对象(例如 Options API 中的 data 对象)时,该方法会非常有用。在引擎盖下,Options API 中的 data 对象使用该方法使其中的所有属性都是反应式的。

But we can create our own reactive object like this:
但我们可以像这样创建自己的反应对象:

import { reactive } from 'vue'

// reactive state
let user = reactive({
        "id": 1,
        "name": "Leanne Graham",
        "username": "Bret",
        "email": "[email protected]",
        "address": {
            "street": "Kulas Light",
            "suite": "Apt. 556",
            "city": "Gwenborough",
            "zipcode": "92998-3874",
            "geo": {
                "lat": "-37.3159",
                "lng": "81.1496"
            }
        },
        "phone": "1-770-736-8031 x56442",
        "website": "hildegard.org",
        "company": {
            "name": "Romaguera-Crona",
            "catchPhrase": "Multi-layered client-server neural-net",
            "bs": "harness real-time e-markets"
        },
        "cars": {
            "number": 0
        }
    })

Here, we imported the reactive method from Vue, and then we declared our user variable by passing its value to this function as an argument. In doing so, we’ve made user reactive, and, thus, if we use user in our template and if either the object or a property of this object should change, then this value will get automatically updated in this template.
在这里,我们从 Vue 中导入了 reactive 方法,然后将 user 变量的值作为参数传递给该函数,从而声明了 user 变量。这样,我们就使 user 具有了反应性,因此,如果我们在模板中使用 user ,并且对象或对象的属性发生变化,那么这个值就会在模板中自动更新。

ref #

Just as we have a method for making objects reactive, we also need one to make other standalone primitive values (strings, booleans, undefined values, numbers, etc.) and arrays reactive. During development, we would work with these other data types while also needing them to be reactive. The first approach we might think of would be to use reactive and pass in the value of the variable that we want to make reactive.
正如我们需要一种方法来使对象具有反应性一样,我们也需要一种方法来使其他独立的原始值(字符串、布尔值、未定义值、数字等)和数组具有反应性。在开发过程中,我们会使用这些其他数据类型,同时也需要它们是反应式的。我们可能会想到的第一种方法是使用 reactive ,然后传入我们想要使其具有反应性的变量的值。

import { reactive } from 'vue'

const state = reactive({
  users: [],
});

Because reactive has deep reactive conversion, user as a property would also be reactive, thereby achieving our goal; hence, user would always update anywhere it is used in the template of such an app. But with the ref property, we can make any variable with any data type reactive by passing the value of that variable to ref. This method also works for objects, but it nests the object one level deeper than when the reactive method is used.
由于 reactive 具有深度反应式转换功能,因此作为属性的 user 也将是反应式的,从而实现了我们的目标;因此, user 将始终更新应用程序模板中使用它的任何地方。有了 ref 属性,我们就可以将任何数据类型的变量值传递给 ref ,从而使该变量成为反应式变量。这种方法同样适用于对象,但它比使用 reactive 方法时将对象嵌套得更深一层。

let property = {
  rooms: '4 rooms',
  garage: true,
  swimmingPool: false
}
let reactiveProperty = ref(property)
console.log(reactiveProperty)
// prints {
// value: {rooms: "4 rooms", garage: true, swimmingPool: false}
// }

Under the hood, ref takes this argument passed to it and converts it into an object with a key of value. This means, we can access our variable by calling variable.value, and we can also modify its value by calling it in the same way.
在引擎盖下, ref 会接收传递给它的参数,并将其转换为一个键值为 value 的对象。这意味着,我们可以通过调用 variable.value 来访问变量,也可以通过同样的方式调用 variable.value 来修改变量的值。来访问变量,并以同样的方式调用它来修改变量值。

import {ref} from 'vue'
let age = ref(1)

console.log(age.value)
//prints 1
age.value++
console.log(age.value)
//prints 2

With this, we can import ref into our component and create a reactive variable:
这样,我们就可以在组件中导入 ref ,并创建一个反应变量:

<template>
  <div class="home">
    <form @click.prevent="">
      <table>
        <tr>
          <th>Name</th>
          <th>Username</th>
          <th>email</th>
          <th>Edit Cars</th>
          <th>Cars</th>
        </tr>
        <tr v-for="user in users" :key="user.id">
          <td>{{ user.name }}</td>
          <td>{{ user.username }}</td>
          <td>{{ user.email }}</td>
          <td>
            <input
              type="number"
              style="width: 20px;"
              name="cars"
              id="cars"
              v-model.number="user.cars.number"
            />
          </td>
          <td>
            <cars-number :cars="user.cars" />
          </td>
        </tr>
      </table>
      <p>Total number of cars: {{ getTotalCars }}</p>
    </form>
  </div>
</template>
<script>
  // @ is an alias to /src
  import carsNumber from "@/components/cars-number.vue";
  import axios from "axios";
  import { ref } from "vue";
  export default {
    name: "Home",
    data() {
      return {};
    },
    setup() {
      let users = ref([]);
      const getUsers = async () => {
        let { data } = await axios({
          url: "data.json",
        });
        users.value = data;
      };
      return {
        users,
        getUsers,
      };
    },
    components: {
      carsNumber,
    },
    created() {
      this.getUsers();
    },
    computed: {
      getTotalCars() {
        let users = this.users;
        let totalCars = users.reduce(function(sum, elem) {
          return sum + elem.cars.number;
        }, 0);
        return totalCars;
    },
  };
</script>

Here, we imported ref in order to create a reactive users variable in our component. We then imported axios to fetch data from a JSON file in the public folder, and we imported our carsNumber component, which we’ll be creating later on. The next thing we did was create a reactive users variable using the ref method, so that users can update whenever the response from our JSON file changes.
在这里,我们导入了 ref ,以便在组件中创建反应式 users 变量。然后,我们导入了 axios ,以便从 public 文件夹中的 JSON 文件获取数据,并导入了我们的 carsNumber 组件,稍后我们将创建该组件。接下来,我们使用 ref 方法创建了一个反应式 users 变量,这样,只要 JSON 文件的响应发生变化, users 就会更新。

We also created a getUser function that fetches the users array from our JSON file using axios, and we assigned the value from this request to the users variable. Finally, we created a computed property that computes the total number of cars that our users have as we have modified it in the template section.
我们还创建了一个 getUser 函数,使用 axios 从 JSON 文件中获取 users 数组,并将该请求的值赋值给 users 变量。最后,我们创建了一个计算属性,用于计算用户拥有的汽车总数,正如我们在模板部分所修改的那样。

It is important to note that when accessing ref properties that are returned in the template section or outside of setup(), they are automatically shallow unwrapped. This means that refs that are an object would still require a .value in order to be accessed. Because users is an array, we could simply use users and not users.value in getTotalCars.
需要注意的是,在访问模板部分或 setup() 以外返回的 ref 属性时,它们会被自动浅层解包。时,它们会被自动浅层解包。这意味着访问对象 refs 时仍需要使用 .value 。由于 users 是一个数组,我们只需在 getTotalCars 中使用 users 而非 users.value 即可。

In the template section, we displayed a table that displays each user’s information, together with a <cars-number /> component. This component accepts a cars prop that is displayed in each user’s row as the number of cars they have. This value updates whenever the value of cars changes in the user object, which is exactly how the data object or computed property would work if we were working with the Options API.
在模板部分,我们展示了一个显示每个用户信息的表格和一个 <cars-number /> 组件。该组件接受一个 cars 道具,在每个用户的行中显示他们拥有的汽车数量。每当用户对象中 cars 的值发生变化时,该值就会更新,如果我们使用选项 API, data 对象或 computed 属性就是这样工作的。

toRefs #

When we use the Composition API, the setup function accepts two arguments: props and context. This props is passed from the component to setup(), and it makes it possible to access the props that the component has from inside this new API. This method is particularly useful because it allows for the destructuring of objects without losing its reactivity.
使用合成 API 时, setup 函数接受两个参数: props 和 context 。这个 props 从组件传递到 setup() 。通过它,我们可以在这个新的 API 中访问组件所拥有的道具。这种方法特别有用,因为它允许在不失去反应性的情况下对对象进行重组。

<template>
  <p>{{ cars.number }}</p>
</template>
<script>
  export default {
    props: {
      cars: {
        type: Object,
        required: true,
      },
      gender: {
        type: String,
        required: true,
      },
    },
    setup(props) {
      console.log(props);
   // prints {gender: "female", cars: Proxy}
    },
  };
</script>
<style></style>

To use a value that is an object from props in the Composition API while ensuring it maintains its reactivity, we make use of toRefs. This method takes a reactive object and converts it into a plain object in which each property of the original reactive object becomes a ref. What this means is that the cars prop…
为了在合成应用程序接口中使用 props 对象的值,同时确保其保持反应性,我们使用了 toRefs 方法。该方法将反应对象转换为普通对象,其中原始反应对象的每个属性都变成了 ref 。这意味着 cars 属性...

cars: {
  number: 0
}

… would now become this:......现在会变成这样:

{
  value: cars: {
    number: 0
  }

With this, we can make use of cars inside any part of the setup API while still maintaining its reactivity.
这样,我们就可以在设置应用程序接口的任何部分使用 cars ,同时仍然保持其反应性。

 setup(props) {
      let { cars } = toRefs(props);
      console.log(cars.value);
      // prints {number: 0}
    },

We can watch this new variable using the Composition API’s watch and react to this change however we might want to.
我们可以使用合成 API 的 watch 来观察这个新变量,并对这一变化做出我们想要的反应。

setup(props) {
      let { cars } = toRefs(props);
      watch(
        () => cars,
        (cars, prevCars) => {
          console.log("deep ", cars.value, prevCars.value);
        },
        { deep: true }
      );
    }

toRef #

Another common use case we could be faced with is passing a value that is not necessarily an object but rather one of the data types that work with ref (array, number, string, boolean, etc.). With toRef, we can create a reactive property (i.e. ref) from a source reactive object. Doing this would ensure that the property remains reactive and would update whenever the parent source changes.
我们可能会遇到的另一个常见用例是,传递的值并不一定是对象,而是与 ref 配合使用的数据类型之一(数组、数字、字符串、布尔等)。有了 toRef ,我们可以从一个源反应式对象创建一个反应式属性(即 ref )。这样做可以确保该属性保持反应性,并在父源发生变化时进行更新。

const cars = reactive({
  Toyota: 1,
  Honda: 0
})

const NumberOfHondas = toRef(state, 'Honda')

NumberOfHondas.value++
console.log(state.Honda) // 1

state.Honda++
console.log(NumberOfHondas.value) // 2

Here, we created a reactive object using the reactive method, with the properties Toyota and Honda. We also made use of toRef to create a reactive variable out of Honda. From the example above, we can see that when we update Honda using either the reactive cars object or NumberOfHondas, the value gets updated in both instances.
在这里,我们使用 reactive 方法创建了一个反应对象,并为其添加了属性 Toyota 和 Honda 。我们还使用 toRef 从 Honda 中创建了一个反应式变量。从上面的示例中我们可以看到,当我们使用反应式 cars 对象或 NumberOfHondas 更新 Honda 时,两个实例中的值都会被更新。时,两个实例中的值都会被更新。

This method is similar and yet so different from the toRefs method that we covered above in the sense that it maintains its connection to its source and can be used for strings, arrays, and numbers. Unlike with toRefs, we do not need to worry about the existence of the property in its source at the time of creation, because if this property does not exist at the time that this ref is created and instead returns null, it would still be stored as a valid property, with a form of watcher put in place, so that when this value changes, this ref created using toRef would also be updated.
该方法与上文介绍的 toRefs 方法相似,但又有很大不同,它能保持与源代码的连接,可用于字符串、数组和数字。与 toRefs 不同与toRefs不同的是,我们不需要担心在创建 ref 时属性是否存在于其源中,因为如果在创建 ref 时该属性并不存在,而是返回 null ,那么它仍将作为一个空值存储。如果在创建 ref 时该属性并不存在,而是返回了 null ,那么它仍将作为一个有效的属性存储,并以 toRefs 的形式存在,这样当该值发生变化时,使用 toRef 创建的 ref 也会随之更新。

We can also use this method to create a reactive property from props. That would look like this:
我们也可以使用此方法从 props 创建一个反应属性。如下所示

<template>
  <p>{{ cars.number }}</p>
</template>
<script>
  import { watch, toRefs, toRef } from "vue";
  export default {
    props: {
      cars: {
        type: Object,
        required: true,
      },
      gender: {
        type: String,
        required: true,
      },
    },
    setup(props) {
      let { cars } = toRefs(props);
      let gender = toRef(props, "gender");
      console.log(gender.value);
      watch(
        () => cars,
        (cars, prevCars) => {
          console.log("deep ", cars.value, prevCars.value);
        },
        { deep: true }
      );
    },
  };
</script>

Here, we created a ref that would be based on the gender property gotten from props. This comes in handy when we want to perform extra operations on the prop of a particular component.
在这里,我们创建了一个基于从 props 获取的 gender 属性的 ref 。当我们想对某个组件的道具执行额外操作时,这就派上用场了。

Conclusion #结论 #

In this article, we have looked at how reactivity in Vue works using some of the newly introduced methods and functions from Vue 3. We started by looking at what reactivity is and how Vue makes use of the Proxy object behind the scenes to achieve this. We also looked at how we can create reactive objects using reactive and how to create reactive properties using ref.
在本文中,我们使用 Vue 3 中新引入的一些方法和函数,探讨了 Vue 中的反应性是如何工作的。我们首先了解了什么是反应性,以及 Vue 如何在幕后使用 Proxy 对象来实现反应性。我们还了解了如何使用 reactive 创建反应对象,以及如何使用 ref 创建反应属性。

Finally, we looked at how to convert reactive objects to plain objects, each of whose properties are a ref pointing to the corresponding property of the original object, and we saw how to create a ref for a property on a reactive source object.
最后,我们了解了如何将反应式对象转换为普通对象,普通对象的每个属性都是一个 ref ,指向原始对象的相应属性,我们还了解了如何为反应式源对象上的一个属性创建一个 ref 。

Further Resources #更多资源 #

(ks, vf, yk, il, al)(KS, VF, YK, IL, AL)
Explore more on探索更多信息

Smashing Newsletter

Tips on front-end & UX, delivered weekly in your inbox. Just the things you can actually use.

Front-End & UX Workshops, Online

With practical takeaways, live sessions, video recordings and a friendly Q&A.

TypeScript in 50 Lessons

TypeScript in 50 Lessons

Everything TypeScript, with code walkthroughs and examples. And other printed books.

— Comments 3

just-mmy
just-mmy wrote #

Good POST!

Ventsislav Iliev
Ventsislav Iliev wrote #

const cars = reactive({ Toyota: 1, Honda: 0 })

const NumberOfHondas = toRef(state, 'Honda')

NumberOfHondas.value++ console.log(state.Honda) // 1

state.Honda++ console.log(NumberOfHondas.value) // 2

where you declare NumberOfHondas shouldn't be using toRef(cars, 'Honda') instead what you wrote toRef(state, 'Honda')? Just asking?

Leave a comment

Comments are moderated. They will be published only if they add to the discussion in a constructive way. If you disagree, please be polite. We all want to learn from each other here. We use GitHub Flavored Markdown for comments. Call out code within a sentence with single backticks (`command`). For a distinct block, use triple backticks (```code block```). Typo? Please let us know.