【Vue3】通过实例,理解 Vue3 的响应式设计

  • Post author:
  • Post category:vue


⭐️ 本文首发自

前端修罗场(点击加入)

,是

一个由资深开发者独立运行的专业技术社区

,我专注

Web 技术、答疑解惑、面试辅导以及职业发展



现在加入,私聊我即可获取一次免费的模拟面试机会

,帮你评估知识点的掌握程度,获得更全面的学习指导意见,交个朋友,不走弯路,少吃亏!


响应式指的是变量(如:数组、字符串、数字、对象等)在其值或它引用的任何其他变量在声明后

发生更改时更新的能力。

在本文中,我们将研究 Vue 中的响应式设计,它是如何工作的,以及我们如何使用新创建的方法和函数来创建响应式变量。


默认情况下,JavaScript 不是响应式的

。 这意味着如果我们创建变量 boy 并在应用程序的 A 部分中引用它,然后在 B 部分继续修改 boy,A 部分将不会更新为boy 的新值。

let framework = 'Vue';
let sentence = `${framework} is awesome`;
console.log(sentence)
 // 输出 "Vue is awesome"
framework = 'React';
console.log(sentence)
// 如果 sentence 是响应式的,输出 react is awesome.

上面的代码片段是 JavaScript

非响应式特性

的经典示例——因此,为什么更改没有反映在 sentence 变量中呢?



Vue 2.x

中,

props、computed 和 data()

默认情况下都是响应式的,

但创建此类组件时数据中不存在的属性除外

。 这意味着

当一个组件被注入到 DOM 中时,只有组件数据对象中的

现有属性

会在这些属性发生变化时导致组件更新

在内部,

Vue 3 使用

Proxy

对象

(ECMAScript 6 功能)来确保这些属性是响应式的,但它仍然提供使用 Vue 2 中的

Object.defineProperty

的选项来支持 Internet Explorer(ECMAScript 5)。 此方法直接在对象上定义新属性,或修改对象上的现有属性,并返回该对象。

乍一看,由于我们大多数人已经知道响应式设计在 Vue 中并不新鲜,因此似乎没有必要使用这些属性,但是

当你处理具有多个可重用函数的大型应用程序时,Options API 有其局限性

。 为此,引入了新的

Composition API 以帮助抽象逻辑

,以使代码库更易于阅读和维护。 此外,我们现在可以使用任何新属性和方法轻松地使任何变量成为响应式,而不管其数据类型如何。

当我们使用 setup 选项作为 Composition API 的入口点时,

数据对象、计算属性和方法是不可访问的

,因为在

执行 setup 时组件实例尚未创建。 这使得无法在 setup 中使用内置的响应式功能。

因此,在本文中,我们将了解我们如何让响应式在这些对象中成为可能。



reactive

根据官方文档,在 Vue 2.6 中等效于

Vue.observable()



reactive

方法在我们尝试创建一个所有属性都是响应式的对象(例如 Options 中的数据对象)。 在底层,Options API 中的数据对象使用此方法来使其中的所有属性都具备响应式特性。

我们可以像这样创建自己的响应式对象:

import { reactive } from 'vue'

// reactive state
let user = reactive({
        "id": 1,
        "name": "Leanne Graham",
        "username": "Bret",
        "email": "Sincere@april.biz",
        "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
        }
    })

在这里,我们从 Vue 导入了

reactive

方法,然后我们通过将其值作为参数传递给该函数来声明我们的用户变量。 在这样做的过程中,我们 user 对象成为响应式。之后,如果我们在模板中使用 user 并且如果该对象的对象或属性发生变化,那么该值将在该模板中自动更新。



ref

正如我们有一种

使

对象

具有响应式的方法

一样,我们也需要一种方法来

使其他独立的

primitive

值(

strings, booleans, undefined values, numbers等

)和数组具有响应式特性

。 在开发过程中,我们将使用这些数据类型,同时还需要它们具有响应式应。 我们可能会想到的第一种方法是使用响应式并传入我们想要使其成为响应式的变量的值。

import { reactive } from 'vue'

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

因为 reactive 有很深的响应式转换,所以 user 作为属性也会是响应式的,从而达到我们的目的。 因此,user 总是会在此类应用程序的模板中使用它的任何地方进行更新。

但是使用 ref 属性,我们可以通过将该变量的值传递给

ref

来使其具有响应式

。 此方法

也适用于对象

,但它比使用

reactive

方法时


将对象嵌套更深一层


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

上述代码中,ref 将这个

property

传递给

reactiveProperty

,并将其转换为一个带有值键的对象。 这意味着,我们可以通过调用

variable.value

来访问我们的变量,也可以

通过同样的方式调用它来修改它的值。

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

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

有了这个,我们可以将 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>

上述代码中,我们导入了 ref 以便在我们的组件中创建一个响应式的 user 变量。 然后我们导入 axios 以从 public 文件夹中的 JSON 文件中获取数据,并且我们导入了我们将在稍后创建的 carsNumber 组件。 我们接下来要做的是使用 ref 方法创建一个响应式用户变量,以便用户可以在我们的 JSON 文件的响应发生变化时进行更新。

我们还创建了一个 getUser 函数,它使用 axios 从我们的 JSON 文件中获取 users 数组,并将此请求中的值分配给 users 变量。 最后,我们创建了一个

计算属性

,用于计算用户拥有的 cars 总数,因为我们在模板部分对其进行了修改。

需要注意的是,当访问在模板部分或

setup()


之外返回

的 ref 属性时,它们会


自动浅展开


。 这意味着作为对象的 refs 仍然需要一个

.value

才能被访问。 因为 users 是一个数组,我们可以在 getTotalCars 中简单地使用 users 而不是 users.value。

在模板部分,我们展示了一个显示每个用户信息的表格,以及一个

<cars-number />

组件。 这个组件接受一个

cars prop

,该prop 显示在每个 user 的行中,作为他们拥有的汽车数量。 每当用户对象中 cars 的值发生变化时,此值就会更新,这正是我们使用 Options API 时数据对象或计算属性的工作方式。



toRefs

当我们使用 Composition API 时,

setup

函数接受两个参数:

props



context

。 这个

props

从组件传递到

setup()

,它使得从这个新 API 中访问组件具有的

props

成为可能。 这种方法特别有用,

因为它允许在不失去响应式的情况下解构对象。

<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);
   // 输出 {gender: "female", cars: Proxy}
    },
  };
</script>
<style></style>

为了在

Composition API

中使用来自

props

的对象的值,同时

确保它保持其响应式

,我们使用了

toRefs

。 此方法


接受一个响应式对象并将其转换为一个普通对象


,其中


原始响应式对象的每个属性都成为一个 ref


这意味着下面这个数据:

cars: {
  number: 0
}

会变成这样:

{
  value: cars: {
    number: 0
}

有了这个,我们可以在

setup

中的任何部分中使用

cars

,同时仍然保持其响应式特性。

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

我们可以使用 Composition API 的

watch

来观察这个新变量,并对这个变化做出我们可能想要的反应。

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



toRef

我们可能面临的另一个常见场景是

传递一个值,该值

不一定是对象

,而是与 ref 一起使用的数据类型之一



array, number, string, boolean等

)。 使用

toRef

,我们可以从源响应式对象创建响应式属性(即 ref)。 这样做将确保该属性保持响应式,并在源响应式数据更改时也进行更新。

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

const NumberOfHondas = toRef(cars, 'Honda')

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

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

上述代码中,我们使用

reactive

方法创建了一个响应式对象 cars,其具有 Toyota 和 Honda 属性。 我们还利用

toRef

从 Honda 创建了一个响应式变量。 从上面的示例中,我们可以看到,当我们使用响应式 cars 对象或 NumberOfHondas 更新 Honda 时,两个实例中的值都会更新。

该方法与我们上面介绍的

toRefs 方法相似但又如此不同




因为它保持与源数据的连接,可用于字符串、数组和数字




toRefs

不同的是,我们

不需要担心在创建时它的源数据中

是否存在该属性


,因为如果在创建此

ref

时该

属性不存在

,而是

返回

null


,它

仍然会被存储 作为一个有效的属性,有一个观察者的形式

,所以当

这个值改变时,这个使用 toRef 创建的 ref 也会被更新

我们也可以使用这个方法从 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>

在这里,我们创建了一个基于从 props 获得的 gender 属性的

ref

。 当我们想要对特定组件的 prop 执行额外操作时,这会派上用场。



写在最后

在本文中,我们使用 Vue 3 中新引入的一些方法和函数来了解 Vue 中的响应式设计师如何工作的。我们首先了解什么是响应式以及 Vue3 如何在底层使用 Proxy 对象来实现这一点。 我们还研究了如何使用

reactive

创建响应式对象以及如何使用 ref 创建响应式属性。

最后,我们研究了如何将响应式对象转换为普通对象,

每个对象的属性都是指向原始对象相应属性的 ref

,并且我们看到了如何为响应式源对象上的属性创建 ref。


如果你觉得这篇文章还不错,请点击下方小红心 👍🏻 ❤️,鼓励一下!我会继续为你带来优质的内容~我是前端修罗场,

一个独立运行的专业技术社区

,感谢你关注我!



版权声明:本文为ImagineCode原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。