1. 计算属性的由来

1.1 普通模板表达式(有缺陷)

<template>
  <span>Name: {{ author.name }}</span>
  <p>Has published books:</p>
  <span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
</template>

<script setup>
import { reactive } from 'vue'
const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})
</script>

<style lang="scss" scoped></style>

在这里插入图片描述
上面代码中

<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>

这行代码虽然能够解决问题,但是有存在一些缺陷:
(1)只能写表达式,不能够写复杂的语句,意味着不能进行复杂的运算;
(2)不够直观,每次都需要完整看完,才能明白表达式的含义;
(3)如果重复使用,代码会变得十分臃肿,并且难以维护。

1.2 计算属性(解决以上问题)

为了解决以上问题,计算属性应运而生。

<template>
  <span>Name: {{ author.name }}</span>
  <p>Has published books:</p>
  <span>{{ isPublishBook }}</span>
</template>

<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

const isPublishBook = computed(() => {
  // 在计算属性里面,我们就对数据进行二次处理
  return author.books.length > 0 ? 'Yes' : 'No'
})

// 计算属性也是响应式,当依赖的数据发生变化,计算属性则会重新计算
setTimeout(() => {
  author.books = []
}, 2000)
</script>

<style lang="scss" scoped></style>

在这里插入图片描述
2s后
在这里插入图片描述
(1)计算属性的书写方式类似函数,支持复杂的运算;
(2)计算属性命名可自定义,一目了然;
(3)依赖于内部的数据,一处变化,处处变化。

完美解决使用模板表达式的缺陷。
当然啦,如果只是一处简单使用,表达式也许更加方便快捷,看情况而定。

2. 可写计算属性(通过结果,逆向修改依赖值,不推荐)

通常情况下,计算属性的作用就是通过(修改)内部的值从而得到需要的值(就是计算属性)。通常来说,这是单向的。

但是可写计算属性就颠覆了这一过程,通过在set函数,可以反向修改其依赖的内部值。比如:

<template>
  <div>{{ firstName }}</div>
  <div>{{ lastName }}</div>
  <div>{{ fullName }}</div>
</template>

<script setup>
import { ref, computed } from 'vue'

const firstName = ref('Sheldon')
const lastName = ref('Zhou')

const fullName = computed({
  // getter
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter
  set(newValue) {
    // 注意:我们这里使用的是解构赋值语法
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})

setTimeout(() => {
  fullName.value = 'Michael Jackson'
}, 3000)
</script>

<style lang="scss" scoped></style>

在这里插入图片描述

在这里插入图片描述
3s后
在这里插入图片描述

3. 最佳实践

3.1 Getter 不应该有副作用

所谓副作用,就是我们在做一件事时,导致其他一些不好的事情发生了。

计算属性的副作用也很简单,就是我们在使用 getter 获取希望得到的值时,不要进行修改其他数据、更改DOM、异步请求等操作,因为这都有可能造成一些副作用,从而造成疑惑,因为没人会想到你会在计算属性中做这些事儿。

常见的副作用操作如下:

  • 发送网络请求
  • 在函数体内修改外部变量的值
  • 使用 console.log 等方法进行输出
  • 调用存在副作用的函数
  • 调用系统 I/O 的 API

3.2 避免直接修改计算属性值

其实就是不推荐使用可写计算属性,因为按照思维惯性,计算属性就应该是根据依赖变化得到的值,相当于一个“临时快照”,通过快照修改原来的依赖,这会显得十分诡异。因此非常不推荐,正常情况下,应该是自然修改依赖,从而改变计算属性的值。

4. 计算属性和方法的区别

4.1 模板中,计算属性重新计算依赖于内部响应式数据变化,方法重新计算依赖于视图变化

计算属性依赖于响应式数据,然后对响应式数据进行二次计算。只有在响应式数据发生变化的时候,才会重新计算,换句话说,计算属性会缓存所计算的值

方法在重新渲染的时候,每次都是重新调用

<template>
  <button v-on:click="a++">A++</button>
  <button v-on:click="b++">B++</button>
  <p>computedA: {{ computedA }}</p>
  <p>computedB: {{ computedB }}</p>
  <p>methodA: {{ methodA() }}</p>
  <p>methodB: {{ methodB() }}</p>
</template>

<script setup>
import { ref, computed } from 'vue'
const a = ref(1)
const b = ref(1)
// 创建两个计算属性,分别依赖 a 和 b
const computedA = computed(() => {
  console.log('计算属性A重新计算了')
  return a.value + 1
})
const computedB = computed(() => {
  console.log('计算属性B重新计算了')
  return b.value + 1
})
function methodA() {
  console.log('methodA执行了')
  return a.value + 1
}
function methodB() {
  console.log('methodB执行了')
  return b.value + 1
}
</script>

<style lang="scss" scoped></style>

在这里插入图片描述
点击按钮A++后,计算属性中只有computedA触发了更新,但是只要视图发生更新,methodA和MethodB也会被重新触发计算。
在这里插入图片描述
在这里插入图片描述
如果将模板中的关于A的代码进行注释

<button v-on:click="a++">A++</button>
<button v-on:click="b++">B++</button>
<!-- <p>computedA: {{ computedA }}</p> -->
<p>computedB: {{ computedB }}</p>
<!-- <p>methodA: {{ methodA() }}</p> -->
<p>methodB: {{ methodB() }}</p>

在这里插入图片描述
你就会发现,不同于之前,无论你怎么点击A++,因为视图没有发生改变,因此根本不会触发methodA和methodB,更别说对应的依赖属性了。

4.2 每次变化,计算属性只计算一次,方法有几次计算几次

每次变化,得益于缓存机制,计算属性只计算一次;
而方法没有缓存,有几次计算几次。

<template>
  <button v-on:click="a++">A++</button>
  <p>computedA: {{ computedA }}</p>
  <p>computedA: {{ computedA }}</p>
  <p>computedA: {{ computedA }}</p>
  <p>methodA: {{ methodA() }}</p>
  <p>methodA: {{ methodA() }}</p>
  <p>methodA: {{ methodA() }}</p>
</template>

<script setup>
import { ref, computed } from 'vue'
const a = ref(1)
// 创建两个计算属性,分别依赖 a 和 b
const computedA = computed(() => {
  console.log('计算属性A重新计算了')
  return a.value + 1
})
function methodA() {
  console.log('methodA执行了')
  return a.value + 1
}
</script>

<style lang="scss" scoped></style>

在这里插入图片描述
在这里插入图片描述

4.3 最佳实践

当数据需要进行二次或以上计算时,使用计算属性即可。
方法通常与事件相关联,作为事件的处理方法进行使用。


上一章 《Vue3 响应式常用API
下一章 《Vue3 Class 与 Style 绑定

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐