Vue3学习之旅--爱上Vue3--Vue3的Options-API--侦听器--计算属性--以及综合案例

本文详细介绍了Vue3中计算属性与方法的区别,强调了计算属性在处理复杂逻辑和数据转换时的优势,如缓存机制。同时,通过案例分析了计算属性的使用,包括getter和setter的运用。此外,还探讨了侦听器watch的用法,如何监听数据变化并执行相应逻辑,以及watch的各种配置选项。最后,通过一个综合案例展示了在实际项目中如何运用计算属性和侦听器来管理购物车应用。
摘要由CSDN通过智能技术生成

Vue3的Options-API

续上文:

vue3学习之旅–邂逅vue3-了解认识Vue3

vue3学习之旅–邂逅vue3-了解认识Vue3(二)

Vue3学习之旅–爱上Vue3–Vue3基础语法(一)–以及vscode基本使用和快速生成代码片段

Vue3学习之旅–爱上Vue3–Vue3基础语法(二)–模板语法–条件渲染–列表渲染–为什么Vue的v-for需要绑定key属性

复杂data的处理方式

我们知道,在模板中可以直接通过插值语法显示一些data中的数据。但是在某些情况,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示;

  1. 比如我们需要对多个data数据进行运算、三元运算符来决定结果、数据进行某种转化后显示
  2. 在模板中使用表达式,可以非常方便的实现,但是设计它们的初衷是用于简单的运算
  3. 在模板中放入太多的逻辑会让模板过重和难以维护
  4. 并且如果多个地方都使用到,那么会有大量重复的代码

我们有没有什么方法可以将逻辑抽离出去呢?

可以,其中一种方式就是将逻辑抽取到一个method中,放到methods的options中,但是,这种做法有一个直观的弊端,就是所有的data使用过程都会变成了一个方法的调用;另外一种方式就是使用计算属性computed

认识计算属性computed

什么是计算属性呢?

官方并没有给出直接的概念解释;而是说:对于任何包含响应式数据的复杂逻辑,你都应该使用计算属性

计算属性将被混入到组件实例中。所有 getter 和 setter 的 this 上下文自动地绑定为组件实例

计算属性的用法

选项:computed

类型:{ [key: string]: Function | { get: Function, set: Function } }

那接下来我们通过案例来理解一下这个计算属性

案例实现思路

我们来看三个案例:

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

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

案例三:我们有一个变量message,记录一段文字:比如Hello World。某些情况下我们是直接显示这段文字;某些情况下我们需要对这段文字进行反转;

我们可以有三种实现思路

  1. 思路一:在模板语法中直接使用表达式
  2. 思路二:使用method对逻辑进行抽取;
  3. 思路三:使用计算属性computed;
实现思路一:模板语法

思路一的实现:模板语法

<template id="my-app">
    <!-- 使用插值语法完成字符串的拼接等案例 -->
    <h2>{{firstName + lastName}}</h2>
    <h2>{{score >= 60 ? '及格' : '不及格'}}</h2>
    <h3>{{message.split('').reverse().join('')}}</h3>
  </template>

缺点一:模板中存在大量的复杂逻辑,不便于维护(模板中表达式的初衷是用于简单的计算);

缺点二:当有多次一样的逻辑时,存在重复的代码;

缺点三:多次使用的时候,很多运算也需要多次执行,没有缓存;

实现思路二:method实现

思路二的实现:method实现

<div id="app"></div>
  <template id="my-app">
    <h2>{{getFullName()}}</h2>
    <h2>{{getResult()}}</h2>
    <h2>{{getReverseMessage()}}</h2>
  </template>
  <script src="../vue3/vue3.js"></script>
  <script>
    Vue.createApp({
      template: '#my-app',
      data() {
        return {
          firstName: '毛',
          lastName: '俊',
          score: 80,
          message: 'hello Vue3!'
        }
      },
      methods: {
        getFullName(){
          return this.firstName + this.lastName
        },
        getResult(){
          return this.score>=60? '及格' : '不及格'
        },
        getReverseMessage(){
          return this.message.split('').reverse().join('')
        }
      },
    }).mount('#app')
  </script>

缺点一:我们事实上先显示的是一个结果,但是都变成了一种方法的调用;

缺点二:多次使用方法的时候,没有缓存,也需要多次计算

思路三的实现:computed实现

思路三的实现:computed实现

<div id="app"></div>
  <template id="my-app">
    <h2>{{fullName}}</h2>
    <h2>{{result}}</h2>
    <h2>{{reverseMessage}}</h2>
  </template>
  <script src="../vue3/vue3.js"></script>
  <script>
    Vue.createApp({
      template: '#my-app',
      data() {
        return {
          firstName: '毛',
          lastName: '俊',
          score: 80,
          message: 'hello Vue3!'
        }
      },
      // 计算属性  计算属性在多次使用时,如果数据没有发生改变,是会重复使用上次的数据的
      computed: {
        // 定义一个计算属性叫做 fullName
        // 虽然计算属性看起来和函数很相似,但是本质上计算属性其实就是一个属性
        fullName() {
          return this.firstName + this.lastName
        },
        result() {
          return this.score >= 60 ? '及格' : '不及格'
        },
        reverseMessage() {
          return this.message.split('').reverse().join('')
        }
      }
    }).mount('#app')
  </script>

注意:计算属性看起来像是一个函数,但是我们在使用的时候不需要加(),这个后面讲setter和getter时会讲到;我们会发现无论是直观上,还是效果上计算属性都是更好的选择;并且计算属性是有缓存的

计算属性 vs methods

在上面的实现思路中,我们会发现计算属性和methods的实现看起来是差别是不大的,而且我们多次提到计算属 性有缓存的

接下来我们来看一下同一个计算多次使用,计算属性和methods的差异:

<div id="app"></div>
  <template id="my-app">
    <button @click="firstName = '毛毛'">修改firstName</button>
    <!-- 表面上 计算属性和方法所达到的效果的确是一样的 -->
    <!-- 但是当我们多次调用计算属性的时候,发现计算属性只被调用一次,因为默认情况下是有缓存的 -->
    <!-- 计算属性会随着数据(也就是data里面我们计算属性用到的那部分数据)的改变,而发生改变 -->
    <h2>{{fullName}}</h2>
    <h2>{{fullName}}</h2>
    <h2>{{fullName}}</h2>
    <h2>{{getFullName()}}</h2>
    <h2>{{getFullName()}}</h2>
    <h2>{{getFullName()}}</h2>
  </template>
  <script src="../vue3/vue3.js"></script>
  <script>
    Vue.createApp({
      template: '#my-app',
      data() {
        return {
          firstName: '毛',
          lastName: '俊'
        }
      },
      // 计算属性  计算属性在多次使用时,如果数据没有发生改变,是会重复使用上次的数据的
      computed: {
        // 定义一个计算属性叫做 fullName
        // 虽然计算属性看起来和函数很相似,但是本质上计算属性其实就是一个属性
        fullName() {
          console.log(1);
          return this.firstName + this.lastName
        }
      },
      methods: {
        getFullName() {
          console.log(2);
          return this.firstName + this.lastName
        }
      },
    }).mount('#app')
  </script>

image-20210722153008321

image-20210722153036987

计算属性的缓存

这是什么原因呢?

  1. 这是因为计算属性会基于它们的依赖关系进行缓存;

  2. 在数据不发生变化时,计算属性是不需要重新计算的;

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

image-20210722153259949

计算属性的setter和getter

计算属性在大多数情况下,只需要一个getter方法即可,所以我们会将计算属性直接写成一个函数(语法糖)。

但是,如果我们确实想设置计算属性的值呢?

这个时候我们也可以给计算属性设置一个setter的方法;

computed: {
        // 直接书写一个计算属性,实际上是简写,调用的是计算属性的getter方法
        // fullName() {
        //   return this.firstName + this.lastName
        // }
        // 计算属性的完整写法
        fullName:{
          get:function(){
            return this.firstName + this.lastName
          },
          set:function(newVal){
            console.log(newVal);
            this.firstName = newVal.split('')[0];
            this.lastName = newVal.split('')[1];
          }
        }
      }
源码如何对setter和getter处理呢?

你可能觉得很奇怪,Vue内部是如何对我们传入的是一个getter,还是说是一个包含setter和getter的对象进行处 理的呢?

事实上非常的简单,Vue源码内部只是做了一个逻辑判断而已;

image-20210722153542978

认识侦听器watch

什么是侦听器呢?

开发中我们在data返回的对象中定义了数据,这个数据通过插值语法等方式绑定到template中;

当数据变化时,template会自动进行更新来显示最新的数据;

但是在某些情况下,我们希望在代码逻辑中监听某个数据的变化,这个时候就需要用侦听器watch来完成了;

侦听器的用法如下:

选项:watch

类型:{ [key: string]: string | Function | Object | Array}

侦听器案例

举个栗子(例子):

比如现在我们希望用户在input中输入一个问题;

每当用户输入了最新的内容,我们就获取到最新的内容,并且使用该问题去服务器查询答案;

那么,我们就需要实时的去获取最新的数据变化;

image-20210722153938057

image-20210722153949432

image-20210722154322714

<div id="app"></div>
  <template id="my-app">
    <label for="question">
      请输入问题:
      <input id="question" type="text" v-model="question">
    </label>

  </template>
  <script src="../vue3/vue3.js"></script>
  <script>
    Vue.createApp({
      template: '#my-app',
      data() {
        return {
          question: 'hello Vue3!'
        }
      },
      // 侦听器  侦听某个属性(数据)的变化,并进行一些逻辑的处理
      // 在实际工作时:我们侦听器一般不会监听计算属性,一般来说都是侦听元数据(data)
      watch: {
        // 第一个参数的变化后的新值
        // 第二个参数是变化前的旧值
        question(newVal, oldVal) {
          console.log(newVal,oldVal);
          this.getAnswer(newVal);
        }
      },
      methods: {
        getAnswer(question) {
          console.log(`${question}问题答案是:哈哈哈哈哈`)
        }
      },
    }).mount('#app')
  </script>
侦听器watch的配置选项

我们先来看一个例子:

当我们点击按钮的时候会修改info.name的值;

这个时候我们使用watch来侦听info,可以侦听到吗?答案是不可以。

这是因为默认情况下,watch只是在侦听info的引用变化,对于内部属性的变化是不会做出响应的:

这个时候我们可以使用一个选项deep进行更深层的侦听;

注意前面我们说过watch里面侦听的属性对应的也可以是一个Object;

还有另外一个属性,是希望一开始的就会立即执行一次:

这个时候我们使用immediate选项;

这个时候无论后面数据是否有变化,侦听的函数都会有限执行一次;

image-20210722154612049

<div id="app"></div>
  <template id="my-app">
    <button @click="info.gender = ''">增加info的gender属性</button>
    <button @click="info.name = '哈哈哈'">改变info的name属性</button>
    <h2 v-for="item of info">{{item}}</h2>

  </template>
  <script src="../vue3/vue3.js"></script>
  <script>
    Vue.createApp({
      template: '#my-app',
      data() {
        return {
          info: {
            name: '毛毛',
            age: 22
          }
        }
      },
      watch: {
        // 直接监听这个info对象,发现属性发生改变了也不会触发
        // 特点: 默认情况下只能监听整个对象是否发生改变(也就是是不是换成一个新对象了)
        // 1. 如果直接将对象info直接重新赋值为一个新的对象时,可以直接侦听到
        // 2. 无法直接侦听到对象内部属性的改变
        // info(newVal, oldVal) {
        //   console.log(newVal, oldVal);
        // }
        // 深度侦听:
        // info: {
        //   // 开启深度侦听,可以侦听到对象内部属性的变化了
        //   deep: true, // 即使对象的属性还是一个对象,仍然还是可以侦听到
        //   handler(newVal, oldVal) {
        //     console.log(newVal, oldVal)
        //   },
        //   // 侦听器的立即执行
        //   immediate: true // 在页面刚加载的时候,数据很明显是没有发生变化的(旧的值相当于不存在,也就是undefined),但是我们仍然想执行一遍侦听器的代码逻辑,就可以让 immediate属性为true
        // }

        // 字符串方法名 侦听器的方法也可以放在methods里面,然后通过字符串的方式赋值,
        // info: "changeInfo" // 实际上用的不多

        // 也可以使用回调数组 会被逐一调用
        info: [
          "changeInfo", // 字符串方法名
          function handler2(newVal, oldVal) {
            console.log(newVal, oldVal, 'handle2');
          },
          {
            handler: function handle3(newVal, oldVal) {
              console.log(newVal, oldVal, 'handle3');
            }
          }
        ]
      },
      methods: {
        changeInfo() {

        }
      },
    }).mount('#app')
  </script>
侦听器watch的其他方式
  1. 字符串方法名

    info: "changeInfo" // 实际上用的不多
    
  2. 回调数组的方式

    // 也可以使用回调数组 会被逐一调用
            info: [
              "changeInfo", // 字符串方法名
              function handler2(newVal, oldVal) {
                console.log(newVal, oldVal, 'handle2');
              },
              {
                handler: function handle3(newVal, oldVal) {
                  console.log(newVal, oldVal, 'handle3');
                }
              }
            ]
    
  3. 另外一个是Vue3文档中没有提到的,但是Vue2文档中有提到的是侦听对象的属性:

    image-20210722154814869

    watch: {
            // 直接侦听对象内的某个属性
            // 发现只有对象的这个顺序发生改变时,才会被侦听到
            // 当然,如果整个对象都直接被替换了,也一样是会被侦听的
            "info.name": function (newVal, oldVal) {
              console.log(newVal, oldVal);
            }
          }
    
    1. 还有另外一种方式就是使用 $watch 的API:

      我们可以在created的生命周期(后续会讲到)中,使用 this.$watchs 来侦听;

      1. 第一个参数是要侦听的源
      2. 第二个参数是侦听的回调函数callback;
      3. 第三个参数是额外的其他选项,比如deep、immediate;
      // 生命周期函数 created 在组件被创建的时候执行(后面会讲)
            created() {
              // 使用当前组件的$watch方法来创建侦听器
              // 参数一:侦听的对象 
              // 参数二:回调函数
              // 参数三:侦听器的配置选项(对象形式) 
              // $watch() 的返回值 unwatch 是一个函数
              // 调用这个返回值函数,可以取消侦听器,而直接定义在watch里面的侦听器一般是没办法取消的
              const unwatch = this.$watch('info', (newVal, oldVal) => {
                console.log(newVal, oldVal);
              }, {
                deep: true,
                immediate: true
              });
              // unwatch() // 调用后 侦听器失效
            }
      

综合案例

现在我们来做一个相对综合一点的练习:书籍购物车

image-20210722155110498

案例说明:

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

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    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;
    }
  </style>
</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="../vue3/vue3.js"></script>
  <script>
    Vue.createApp({
      template: '#my-app',
      data() {
        return {
          books: [
            {
              id: 1,
              name: '《算法导论》',
              date: '2006-9',
              price: 85.00,
              count: 1
            },
            {
              id: 2,
              name: '《UNIX编程艺术》',
              date: '2006-2',
              price: 59.00,
              count: 1
            },
            {
              id: 3,
              name: '《编程珠玑》',
              date: '2008-10',
              price: 39.00,
              count: 1
            },
            {
              id: 4,
              name: '《代码大全》',
              date: '2006-3',
              price: 128.00,
              count: 1
            },
          ]
        }
      },
      computed: {
        // vue2 filter/map/reduce
        totalPrice() {
          let finalPrice = 0;
          for (let book of this.books) {
            finalPrice += book.count * book.price;
          }
          return finalPrice;
        },
        // Vue3不支持过滤器了, 推荐两种做法: 使用计算属性/使用全局的方法
        filterBooks() {
          const newBook = this.books.map(item => {
            // 生成新的对象
            const newItem = Object.assign({}, item);
            newItem.price = "¥" + item.price;
            return newItem;
          })
          return newBook
        }
      },
      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')
  </script>
</body>

</html>
  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

尤雨东

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值