首页 > 编程笔记 > JavaScript笔记 阅读:19

Vue3计算属性用法详解(附带实例)

前面详细讲解了 Vue3 模板的基本语法,可以发现在模板中使用表达式是非常便利的,但是表达式更适用于简单运算,如果使用表达式来处理复杂的逻辑,则会让模板变得非常臃肿且难以维护。

Vue 为了帮助用户处理复杂的逻辑,提供了计算属性和监听,本节将对计算属性进行讲解。

Vue计算属性的基本使用

借助三目表达式,我们可以动态判断data中score的值,从而决定显示的文本。这个动态判断的过程可以称为计算。这里将三目表达式的模板编码单列出来,并对其进行分析:
<p>{{ score < 60 ? 'C语言测试未通过,暂时不可以学数据结构' : 'C语言测试通过,可以继续学习数据结构' }}</p>
上面的模板看起来有些复杂,可读性较差。而对于这段代码来说,更重要的是,如果页面中有多处需要同样的计算,这个计算的模板就需要重复编写多遍。

为了解决这一问题,Vue 框架专门设计了一个重要语法:计算属性。当模板显示的某个数据需要通过已有数据进行一定的逻辑计算才能确定时,就可以选择用计算属性语法来实现。

先来看利用计算属性语法重构后的代码,具体如下:
<div id="app">
  <!-- 读取 data 数据显示 -->
  <h2>测评得分:{{score}}</h2>
  <!-- 读取计算属性数据显示 -->
  <h3>测评结果:{{resultText}}</h3>
</div>

<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.2.31/vue.global.min.js"></script>
<script>
const { createApp } = Vue

createApp({
  data () {
    return {
      score: 59
    }
  },
  // 所有计算属性都被定义在 computed 配置对象中
  computed: {
    // 一个函数类型的计算属性,返回计算的结果
    resultText () {
      console.log('computed resultText()')
      // 根据 score 进行计算,产生模板需要显示的结果
      const result = this.score < 60 ? 'C语言测试未通过,暂时不可以学数据结构' : 'C语言测试通过,可以继续学数据结构'
      // 返回结果数据
      return result
    }
  },
}).mount('#app')
</script>
重点关注代码中的配置对象 computed,实际上这就是 Vue 中计算属性定义的位置,整个代理对象需要的计算属性都需要定义在 computed 配置对象中。

我们结合上面这段代码来分析,“测评得分”的数据就是在读取 data 数据,这并没有什么特别的,我们不对其多做讲解。“测评结果”需要根据“测评得分”来显示对应的结果,该结果需要根据 score 的数据来决定,这满足了计算属性的条件,因此使用了计算属性来实现。

这里在 computed 配置对象中定义了名称为 resultText 的计算属性,它本质上就是一个函数,内部可以根据已有的 data 数据进行计算,产生要显示的结果数据并返回它。

需要注意的是,计算属性函数中的 this 就是当前组件对象,通过它可以直接访问 data 中的数据。在模板中可以读取计算属性函数 resultText,当初始化显示时,系统会自动调用这个计算属性函数得到返回值并显示到页面上。

此时,data 中 score 的值为 59,因此返回的“测评结果”为“C语言测试未通过,暂时不可以学数据结构”。页面效果如下图所示。


图 1 页面效果

如果更新了计算属性依赖的数据会怎么样呢?

比如我们更新了 data 中的 score,那么计算属性函数 resultText 会重新执行计算并返回新的结果值,对应的页面元素会自动更新。下面通过代码对其进行测试。
<div id="app">
  <!-- 读取 data 数据显示 -->
  <h2>测评得分:{{score}}</h2>
  <!-- 读取计算属性数据显示 -->
  <h3>测评结果:{{resultText}}</h3>
  <button @click="reExam">学习一段时间后重新测评</button>
</div>

<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.2.31/vue.global.min.js"></script>
<script>
const { createApp } = Vue

createApp({
  data () {
    return {
      score: 59
    }
  },
  computed: {
    resultText () {
      console.log('computed resultText()')
      const result = this.score < 60 ? 'C语言测试未通过,暂时不可以学数据结构' : 'C语言测试通过,可以继续学数据结构'
      return result
    }
  },
  methods: {
    // 更新 data 中的 score
    reExam () {
      this.score += 10
    }
  }
}).mount('#app')
</script>
我们为其增加了一个按钮“学习一段时间后重新测评”,当点击该按钮更新 score 的值后,h2 标签中的内容也会被更新。同时计算属性函数 resultText 重新执行返回新的值,h3 标签也显示为最新的计算属性值,如下图所示。


图 2 点击按钮前后

这里需要注意的是,每次更新依赖数据,计算属性函数都会重新执行计算。

Vue计算属性和method方法

前面计算属性的效果还可以通过在模板中调用 method 方法实现,它的实现结果与计算属性是一样的。

下面通过在模板中调用 method 方法来实现前面测评结果的功能。模板代码片段如下:
<!-- 调用 method 方法显示 -->
<h4>测评结果[method 实现]:{{getResultText()}}</h4>

methods 代码片段如下:
methods: {
    getResultText () {
        console.log('method getResultText()')
        const result = this.score < 60 ? 'C语言测试未通过,暂时不可以学数据结构' : 'C语言测试通过,可以继续学数据结构'
        return result
    }
}
无论是初始化显示还是更新显示,其效果跟计算属性的效果都是一样的。

尽管如此,我们在开发时还是会选择使用计算属性来实现。这是因为计算属性内会将计算属性函数返回的结果数据进行缓存处理,如果结果数据需要在页面中显示多次,那么计算属性函数只会执行 1 次,但 method 方法会对应执行多次。这样对比下来,计算属性的效率明显要比方法高。

下面修改一下模板,对计算属性和 method 方法再次进行测试:
<div id="app">
    <!-- 读取 data 数据显示 -->
    <h2>测评得分:{{score}}</h2>
    <!-- 读取计算属性数据显示 3 处 -->
    <h3>测评结果[computed 实现]:{{resultText}}</h3>
    <h3>测评结果[computed 实现]:{{resultText}}</h3>
    <h3>测评结果[computed 实现]:{{resultText}}</h3>
    <!-- 调用 method 方法显示 3 处 -->
    <h4>测评结果[method 实现]:{{getResultText()}}</h4>
    <h4>测评结果[method 实现]:{{getResultText()}}</h4>
    <h4>测评结果[method 实现]:{{getResultText()}}</h4>
    <button @click="reExam">学习一段时间后重新测评</button>
</div>
当页面初始化显示时,计算属性函数只执行了 1 次,而 method 方法执行了 3 次。点击按钮更新,计算属性函数只执行了 1 次,而 method 方法执行了3次。

因此当有可能存在结果数据需要显示多次的情况时,明显应该选择计算属性,因为其效率更高,但如果确定只显示一次,则 method 方法也是一个可以接受的选择。

Vue计算属性的setter

计算属性在默认情况下仅能通过计算属性函数得出结果。当开发者尝试修改一个计算属性时,会收到一个运行时警告提示。我们来看如下图所示的页面效果。


图 3 页面效果

现有 3 个需求,具体如下:
  1. 姓名由“姓-名”组成,姓名的初始显示为“A-B”;
  2. 当改变姓或名时,姓名能自动同步变化;
  3. 姓和名能实时与姓名同步。

下面对数据进行分析和设计。可以将“姓”和“名”设计为 2 个 data 数据,我们可以利用 v-model 实现双向数据绑定,但因为“姓名”是由“姓”和“名”动态确定的,所以我们可以将“姓名”设计为计算属性,同样用 v-model 绑定到 input 标签上。

具体实现代码如下:
<div id="app">
    <p>
        姓:<input type="text" v-model="firstName">
    </p>
    <p>
        名:<input type="text" v-model="lastName">
    </p>
    <p>
        姓名:<input type="text" placeholder="格式:姓-名" v-model="fullName">
    </p>
</div>

<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.2.31/vue.global.min.js"></script>
<script>
const { createApp } = Vue

createApp({
    data () {
        return {
            firstName: 'A',
            lastName: 'B'
        }
    },
    computed: {
        fullName () {
            return this.firstName + '-' + this.lastName
        }
    }
}).mount('#app')
</script>
在上面的代码中,我们在 data 对象中定义了 firstName 和 lastName 两个计算属性,在 computed 配置中定义了计算属性 fullName,并通过 v-model 将其绑定到对应的 input 标签上。

初始显示实现了需求 1 中“姓名”的显示要求,如下图所示:


图 4 初始显示

当修改“姓”或“名”的内容时,“姓名”也会自动同步变化,这样也实现了需求 2 中“姓名”同步变化的要求,如下图所示:


图 5 数据同步改变

那么问题来了,当我们在输入框(input)中改变“姓名”的内容时,v-model 会自动将输入值赋给计算属性 fullName,Vue 框架会抛出警告提示,如下图所示:


图 6 抛出警告提示

警告提示表示写操作失败,因为计算属性 fullName 是只读的,也就是只能计算返回一个值。那么应该如何设置计算属性值呢?答案是:可以通过同时提供 getter 和 setter 的计算属性来实现。

下面修改一下计算属性 fullName 的实现,代码如下:
computed: {
  fullName: {
    get () {
      console.log('fullName,get()')
      return this.firstName + '-' + this.lastName
    },
    set (value) {
      console.log('fullName set() ', value)
      const names = value.split('-')
      this.firstName = names[0]
      this.lastName = names[1]
    }
  }
}
此时的计算属性 fullName 是一个对象,包含 get 方法(常称为 getter)和 set 方法(常称为 setter)。

前面写的计算属性函数的功能等同于 get 方法,当它在初始化时会执行一次,并且任意依赖数据发生变化时会再次执行,也就实现了“姓名”的初始动态显示与修改“姓”或“名”的内容时会同步更新显示的功能。而 set 方法则是在修改 fullName 属性值后,会自动执行。

也就是说,当修改“姓名”的内容时,计算属性的 set 方法就会自动执行,在 set 方法中接收 fullName 指定的最新值,并分隔出两个值的数组分别去更新 firstName 和 lastName,这样就实现了需求 3 中的要求。

相关文章