Vue3中使用optionsAPI(三)

421 阅读4分钟

optionsAPI

我们知道,在模板中可以直接通过插值语法显示一些data中的数据

在某些情况我们可能会对于数据进行复杂的处理展示

按以前的做法是我们在模板中使用表达式或者将逻辑抽离到method中

但是显然还有另一种更好的方式就是computed

1.计算属性computed

要用的属性不存在,要通过已有属性计算得来

底层借助了Objcet.defineproperty方法提供的getter和setter

对于任何响应式数据的复杂逻辑,实际都应该优先计算属性

里面的计算属性可以为函数或者对象

我们通过三个案例来学习一下

案例一:我们有两个变量:firstName和lastName,希望它们拼接之后在界面上显示

案例二:我们有一个分数score,当score大于60的时候,在界面上显示及格,当score小于60的时候,在界面上显示不及格

案例三:我们有一个变量message,记录一段文字:比如Hello World,对其反转

image-20220129140031877

实现一:

  • 使用插值语法,模板存在大量逻辑不利于维护和复用,多次使用也没有缓存

image-20220129135205756

实现二:

  • 使用methods,显示值的地方变成了函数调用,多次调用也没有缓存

image-20220129135406159

实现三:

  • computed实现,使用时候不需要加(),而且会缓存对应的值

image-20220129135731764

计算属性的缓存

计算属性会基于自身的依赖关系进行缓存,methods不会

当我们多次使用相同计算属性时, 计算属性中的运算只会执行一次

但是如果依赖的数据发生变化,在使用时,计算属性依然会重新进行计算

所以get函数会在初次读取时会执行一次,当依赖的数据发生改变时会被再次调用

image-20220129140833504

计算属性的setter和getter

计算属性绝大多数情况只需要一个getter方法即可,所以我们会写成一个函数

如果想设置计算属性的值。我们也可以设置一个setter方法

image-20220129141130518

Vue3源码对于settergetter内部做了逻辑判断,

image-20220129145118382

2.侦听器watch

当被监视的属性变化时, 回调函数自动调用, 进行相关操作

我们如果希望在代码逻辑中监听某个数据的变化,这个时候就需要用侦听器watch来完成了

比如现在我们希望用户在input中输入一个问题,每当用户输入了最新的内容,我们就获取到最新的内容,并且使用该问题去服务器查询答案

image-20220129150336331

侦听器watch的配置选项

先看一个场景:

当我们侦听info对象,但是点击按钮修改info.name的值,这是不能被侦听到变化的

默认情况下watch只是在侦听ino的引用变化,对于内部的属性变化不会响应

这时候我们就需要使用一个选项deep进行深层的侦听了

并且如果我们希望一开始侦听器就执行一次可以使用immediate选项

当变更的对象或数组使用deep选项时,旧值和新值会指向同一个对象或数组,因为引用相同,所以Vue不会保留之前值的副本

image-20220129155033133

侦听对象的属性:

image-20220129160254408

使用 $watch 的API:

image-20220129160604845

3.computed和watch之间的区别

  • computed能完成的功能,watch都可以完成
  • watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作
image-20220130202419975

两个重要的原则:

  • 所被Vue管理的函数,最好写成普通函数,这样this的指向才是app或 组件实例对象
  • 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是app或组件实例对象

4.综合案例

image-20220129160709921

  • 在界面上以表格的形式,显示一些书籍的数据
  • 在底部显示书籍的总价格
  • 点击+或者-可以增加或减少书籍数量(如果为1,那么不能继续 - )
  • 点击移除按钮,可以将书籍移除(当所有的书籍移除完毕时,显示:购物车为空~)

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <link rel="stylesheet" href="./style.css" />
  </head>
  <body>
    <div id="app"></div>

    <template id="my-app">
      <template v-if="books.length > 0">
        <table>
          <thead>
            <th>序号</th>
            <th>书籍名称</th>
            <th>出版日期</th>
            <th>价格</th>
            <th>购买数量</th>
            <th>操作</th>
          </thead>
          <tbody>
            <tr v-for="(book, index) in books">
              <td>{{index + 1}}</td>
              <td>{{book.name}}</td>
              <td>{{book.date}}</td>
              <td>{{formatPrice(book.price)}}</td>
              <td>
                <button :disabled="book.count <= 1" @click="decrement(index)">-</button>
                <span class="counter">{{book.count}}</span>
                <button @click="increment(index)">+</button>
              </td>
              <td>
                <button @click="removeBook(index)">移除</button>
              </td>
            </tr>
          </tbody>
        </table>
        <h2>总价格: {{formatPrice(totalPrice)}}</h2>
      </template>
      <template v-else>
        <h2>购物车为空~</h2>
      </template>
    </template>

    <script src="../js/vue.js"></script>
    <script src="./index.js"></script>
  </body>
</html>

style.css

table {
  border: 1px solid #e9e9e9;
  border-collapse: collapse;
  border-spacing: 0;
}

th,
td {
  padding: 8px 16px;
  border: 1px solid #e9e9e9;
  text-align: left;
}

th {
  background-color: #f7f7f7;
  color: #5c6b77;
  font-weight: 600;
}

.counter {
  margin: 0 5px;
}

index.js

Vue.createApp({
  template: "#my-app",
  data() {
    return {
      books: [
        {
          id: 1,
          name: "《算法导论》",
          date: "2006-9",
          price: 85.0,
          count: 1,
        },
        {
          id: 2,
          name: "《UNIX编程艺术》",
          date: "2006-2",
          price: 59.0,
          count: 1,
        },
        {
          id: 3,
          name: "《编程珠玑》",
          date: "2008-10",
          price: 39.0,
          count: 1,
        },
        {
          id: 4,
          name: "《代码大全》",
          date: "2006-3",
          price: 128.0,
          count: 1,
        },
      ],
    };
  },
  computed: {
    totalPrice() {
      let finalPrice = 0;
      for (let book of this.books) {
        finalPrice += book.count * book.price;
      }
      return finalPrice;
    },
    // Vue3不支持过滤器了, 推荐两种做法: 使用计算属性/使用全局的方法
    filterBooks() {
      return this.books.map((item) => {
        const newItem = Object.assign({}, item);
        newItem.price = "¥" + item.price;
        return newItem;
      });
    },
  },
  methods: {
    increment(index) {
      // 通过索引值获取到对象
      this.books[index].count++;
    },
    decrement(index) {
      this.books[index].count--;
    },
    removeBook(index) {
      this.books.splice(index, 1);
    },
    formatPrice(price) {
      return "¥" + price;
    },
  },
}).mount("#app");