[TOC]

计算属性 computed

复杂 data 的处理方式

在模板中可以直接通过插值语法显示一些data中的数据。但是在某些情况,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示。
案例一:我们有两个变量:firstName和lastName,希望它们拼接之后在界面上显示;
案例二:我们有一个分数:score

  • 当score大于60的时候,在界面上显示及格;
  • 当score小于60的时候,在界面上显示不及格;

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

  • 某些情况下我们是直接显示这段文字;
  • 某些情况下我们需要对这段文字进行反转;

对于这种复杂 data 数据处理,有三种方式:

  1. 在模板语法中直接使用表达式
  2. 使用 method 对逻辑进行抽取
  3. 计算属性

    模板语法

    ```html
``` 缺点一:模板中存在大量的复杂逻辑,不便于维护(模板中表达式的初衷是用于简单的计算);
缺点二:当有多次一样的逻辑时,存在重复的代码;
缺点三:多次使用的时候,很多运算也需要多次执行,没有缓存; ### method 逻辑抽取实现 ```html ``` 缺点一:我们事实上想要显示的是一个结果,但是都变成了一种方法的调用;
缺点二:多次使用方法的时候,没有缓存,也需要多次计算; ## 认识计算属性 computed 什么是计算属性呢? - 官方并没有给出直接的概念解释; - 而是说:**对于任何响应式数据的存在复杂逻辑,就应该使用计算属性**; - 计算属性将被混入到组件实例中。所有 getter 和 setter 的 this 上下文自动地绑定为组件实例; - 也就是说 this 可以访问 data 中的数据 计算属性的用法: - options:computed - 类型:{ [key: string]: Function | { get: Function, set: Function } } 本质是一个对象,对象中有 setter 和 getter;如果直接写成一个函数,那就是 getter,其实就是对象形式的语法糖。 ```html ``` 计算属性的对象形式 ```html ``` ## 计算属性的缓存 计算属性的值会基于它们的依赖关系进行缓存;在数据不发生变化时,计算属性是不需要重新计算的,也就是多次使用,计算属性只要执行一次。而 methods 中的方法使用几次就需要几次执行。 ```html ``` ## vue 如何对setter和getter处理呢? vue 内部是如何对我们传入的是一个getter,还是说是一个包含setter和getter的对象进行处
理的呢?
事实上非常的简单,Vue源码内部只是做了一个逻辑判断而已;
![image.png](https://cdn.nlark.com/yuque/0/2022/png/22919157/1651254138398-a2b72eb3-79fa-4c58-a199-a27df0887ff8.png#clientId=u84fc990c-7d8b-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=334&id=u3cb52173&margin=%5Bobject%20Object%5D&name=image.png&originHeight=418&originWidth=599&originalType=binary&ratio=1&rotation=0&showTitle=false&size=173074&status=done&style=none&taskId=u3eda7059-264e-4d2e-9435-9f986e1a5fe&title=&width=479.2) # 侦听器 watch 什么是侦听器呢?
开发中我们在data返回的对象中定义了数据,这个数据通过插值语法等方式绑定到template中;当数据变化时,template会自动进行更新来显示最新的数据;
但是在某些情况下,我们希望**在代码逻辑中监听某个数据的变化**,这个时候就需要用侦听器 watch 来完成了; 注意和计算属性区分,计算属性是依赖于 data 元数据,进行计算得出新属性;监听器是监听到元数据的变化就执行一段代码逻辑。 侦听器的用法如下: - 选项:watch - 类型:{ [key: string]: string | Function | Object | Array} watch 也是个对象类型,属性 key 就是要侦听的 data 数据,value 可以是字符串,对象,数组。还可以是函数,一般函数也是用的最多,但本质是对象 handler 属性的语法糖。 举个栗子(例子):
比如现在我们希望用户在 input 中输入一个问题;每当用户输入了最新的内容,我们就获取到最新的内容,并且使用该问题去服务器查询答案;那么,我们就需要实时的去获取最新的数据变化; ```html ``` - value 为字符串:表示调用 methods 中的方法 ```javascript watch: { inputVal: 'qwer' // 调用 qwer 方法 }, methods: { qwer() { console.log('qwer') } } ``` 如果监听到 data 数据的变化,想要执行多个处理函数,则可以用数组 ```javascript watch: { inputVal: [ 'qwer', function a(newVal, oldVal) { console.log(`${oldVal}->${newVal}`) }, { handler: function () { console.log('hhh'); } } ] }, methods: { qwer() { console.log('qwer') } } ``` ## 侦听器 watch 的配置选项 当监听的数据类型是个引用类型的话,比如对象。对象中的属性变化,watch 是监听不到的。它只能监听到这个对象被整体赋值改变。
在对象的形式中不止有 handler 属性,还有 `deep`。 deep 属性表示是否进行更深层的侦听。
还有一个属性`immediate`,表示立即执行,也就是 data 数据第一次更新的时候,网页刚刚渲染 data 从 undefined 变成有值的过程能被监听到。监听函数至少被执行了一次。 ```javascript watch: { inputVal: { handler(newVal, oldVal) { console.log(`${oldVal} 变成 ${newVal},发送查询请求`); }, deep: true, // 深层监听 immediate: true // 立即执行 } } ``` 对于 deep 深层监听,存在一个弊端,那就是监听了对象中所有属性的变化,如果想要精确监听某一个属性,可以直接指明。
还有一个注意点:deep 深层监听,newVal 和 oldVal 是一样的,因为只是进行了浅拷贝,都是指向同一个地址。 ```html ``` 如果想要监听数组中对象的某个属性,精确监听是监听不到的,有两种方式: 1. 监听整个数组,并且开启深度监听,但是这样不必要的监听太多。不推荐 2. 通过组件 props 监听 ## $watch $watch 函数是 vue 提供的另一种实现监听 API。我们可以在 created 的生命周期中,使用 `this.$watchs` 来侦听。 - 第一个参数是要侦听的源; - 第二个参数是侦听的回调函数callback; - 第三个参数是额外的其他选项,比如deep、immediate; ```javascript created() { this.$watch( 'info', // $watch 是函数有作用域,向上找就是模板实例,所以可以用箭头函数 (newVal, oldVal) => { console.log(`${oldVal} -> ${newVal}`)}, {deep: true, immediate: true} ) } ``` # 综合案例 1. 在界面上以表格的形式,显示一些书籍的数据; 2. 在底部显示书籍的总价格; 3. 点击+或者-可以增加或减少书籍数量(如果为1,那么不能继续-); 4. 点击移除按钮,可以将书籍移除(当所有的书籍移除完毕时,显示:购物车为空~); ```javascript

```javascript
Vue.createApp({
  template: '#templ',
  data() {
    return {
      books: [
        {
          id: 1,
          name: '《算法导论》',
          date: '2006-9',
          price: 80.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
        }
      ],
      showFlag: true
    }
  },
  methods: {
    reduce(index) {
      // if(this.books[index].count > 1)  this.books[index].count--
      this.books[index].count--
    },
    add(index) {
      this.books[index].count++
    },
    removeBook(id) {
      this.books = this.books.filter(item => {
        return item.id != id
      })
    }
  },
  computed: {
    totalPrice() {
      let result = 0
      if (this.books.length == 0) return 0
      for (const book of this.books) {
        result += book.count * book.price
      }
      return result
    }
  },
  watch: {
    'books.length': function () {
      this.books.length <= 0 ? (this.showFlag = false) : (this.showFlag = true)
    }
  }
}).mount('#app')

其中对数据的格式化,比如价格前面加上人民币符号。在 vue2 中可以利用过滤器,但是 vue3 中删除了过滤器,推荐使用计算属性和全局方法来代替过滤器的功能。

// Vue3不支持过滤器了, 推荐两种做法: 使用计算属性/使用全局的方法
filterBooks() {
  return this.books.map(item => {
    const newItem = Object.assign({}, item);
    newItem.price = "¥" + item.price;
    return newItem;
  })
}

表单指令:v-model

表单提交是开发中非常常见的功能,也是和用户交互的重要手段:

  • 比如用户在登录、注册时需要提交账号密码;
  • 比如用户在检索、创建、更新信息时,需要提交一些数据;

这些都要求我们可以在代码逻辑中获取到用户提交的数据,我们通常会使用 v-model 指令来完成:

  • v-model 指令可以在表单 input、textarea 以及 select 元素上创建双向数据绑定
  • 它会根据控件类型自动选取正确的事件来获取表单输入
    • v-bind 是从数据源到模板,v-model 通过控件类型对应的事件回调函数,在响应函数中通过事件对象 event 来获取输入内容,从而实现了数据从模板到 data

所以 v-model 本质上不过是 v-bind 和 v-on 的语法糖

v-model 是语法糖

比如:input 输入框,类型 type 为 text,在输入框中输入的内容保存在 value 属性中。v-model 实现了 v-bind 绑定value 属性,并且监听了 input 事件,通过事件对象获取了输入内容。

<template id="templ">
  <input type="text" :value="val" @input="setVal($event)">
  <h2>上面输入框的值:{{val}}</h2>
  <button @click="change">修改输入框的值</button>
</template>

<script>
  Vue.createApp({
    template: "#templ",
    data() {
      return {
        val: null
      }
    },
    methods: {
      // 模板输入,data 获取到输入的值
      setVal(event) {
        console.log(event);
        this.val = event.target.value
      },
      // data 改变,模板数据也改变
      change() {
        this.val += 1
      }
    },
  }).mount("#app")
</script>

上面的实现和下面的实现,效果是一样的。

<template id="templ">
  <input type="text" v-model="inputValue">
  <h2>上面输入框的值:{{inputValue}}</h2>
</template>

<script>
  Vue.createApp({
    template: "#templ",
    data() {
      return {
        val: null,
        inputValue: null
      }
    }
  }).mount("#app")
</script>

但是 v-model 实际上肯定比单纯的 v-bind 和 v-on 更复杂,因为 v-model 最大的特点是实现了针对不同类型的表单控件,自动绑定对应的属性和事件。
比如:

  • text 和 textarea 元素绑定valueinput 事件;
  • checkbox 和 radio 元素绑定checkedchange 事件;
  • select 元素绑定 value 并将 change 作为事件。

image.png

v-model 绑定单、多选框

注意下 v-model 绑定多选框,我们知道多选框获取“输入”的是 chenked 属性,“输入”也是个布尔值,显然我们获取布尔值是没有多大意义的,我们想要的是选中这个选项的真实含义。
此时就可以手动添加一个 value 属性,value 属性中写上这个选项代表的含义。而 v-model 会优先将 value 属性的值传递到 data 中。

<template id="templ">
  <span>你的爱好: </span>
  <label for="basketball">
    <!-- 显示地写出 value 属性 -->
    <input id="basketball" type="checkbox" v-model="hobbies" value="basketball"> 篮球
  </label>
  <label for="football">
    <input id="football" type="checkbox" v-model="hobbies" value="football"> 足球
  </label>
  <label for="tennis">
    <input id="tennis" type="checkbox" v-model="hobbies" value="tennis"> 网球
  </label>
  <!-- 将会把 value 值传入 hobbies,而不是一个一个选中标记 -->
  <h2>hobbies: {{hobbies}}</h2>
</template>

<script>
  Vue.createApp({
    template: "#templ",
    data() {
      return {
        hobbies: []
      }
    }
  }).mount("#app")
</script>

v-model 的值绑定

目前我们在前面的案例中大部分的值都是在template中固定好的:

  • 比如gender的两个输入框值male、female;
  • 比如hobbies的三个输入框值basketball、football、tennis;

在真实开发中,我们的数据可能是来自服务器的,那么我们就可以先将值请求下来,绑定到data返回的对象中,
再通过 v-bind 来进行值的绑定,这个过程就是值绑定。

v-model 修饰符

lazy

lazy修饰符是什么作用呢?
默认情况下,v-model 在进行双向绑定时,绑定的是input事件,那么会在每次内容输入后就将最新的值和绑定
的属性进行实时同步;
如果我们在 v-model 后跟上lazy修饰符,那么会将绑定的事件切换为 change 事件,只有在提交时(比如回车)
才会触发,这样就不会实时绑定,只有手动触发事件后才绑定;

<input type="text" v-model.lazy="message">
<h2>{{ message }}</h2>

number

v-model 有个特点,会将所有获取到的输入值,都以字符串的形式传入 data。
比如输入数字,其实在 data 中是一个数字字符串,虽然不影响逻辑判断,因为逻辑判断会有隐式转换,但是如果想要以 number 类型保存到 data 中,可以加个 number 修饰符。

<template id="templ">
  <input type="text" v-model="inputVal1" placeholder="输入 123">
  <input type="text" v-model.number="inputVal2" placeholder="输入 123">
</template>

<script>
  Vue.createApp({
    template: "#templ",
    data() {
      return {
        inputVal1: null,
        inputVal2: null
      }
    },
    watch: {
      inputVal1() {
        console.log(typeof this.inputVal1); // string
      },
      inputVal2() {
        console.log(typeof this.inputVal2); // number
      }
    }
  }).mount("#app")
</script>

trim

如果要自动过滤用户输入的首尾空白字符,可以给v-model添加 trim 修饰符

v-model 组件上使用