Vue 计算属性(1)

367 阅读4分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

这里讲的是 Options API 中的计算属性,后面我们还会讲 Composition API 中的计算属性(在 setup 中使用计算属性)。

在讲计算属性之前,我们先来讨论一下 Vue 中关于复杂 data 的处理方式:

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

  • 但是在某些情况下,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来显示:

    • 比如我们需要data 中的多个数据进行运算通过三元运算符来决定结果数据进行某种转化后显示;
    • 在模板中使用表达式,可以非常方便地实现,但是这种语法的设计初衷是用于简单的运算
    • 在模板中放入太多的逻辑会让模板过重且难以维护
    • 并且如果多个地方都使用到,那么会有大量重复的代码;
  • 那有没有办法可以将逻辑抽离出去呢?

    • 可以,一种方式是将逻辑抽取到一个方法(method)中,放到 methods 选项中;
    • 但这种方式有一个直观的弊端,就是让 data 中数据的使用过程变成了方法的调用
    • 另一种方式就是使用计算属性(computed)了;

那么什么是计算属性呢?

  • 官方并没有给出直接的概念解释;
  • 而是说:对于任何包含响应式数据的复杂逻辑,你都应该使用计算属性
  • 计算属性将被混入到组件实例中。所有 gettersetterthis 上下文自动地绑定为组件实例。

计算属性的用法:

  • 选项:computed
  • 类型:{ [key: string]: Function | { get: Function, set: Function } }

下面,我们通过案例来理解一下这个计算属性。

1. 案例演示

我们来看三个案例:

  1. 我们有两个变量:firstNamelastName,希望它们拼接之后在界面上显示;
  2. 我们有一个分数:score
    • score 大于等于 60 时,在界面上显示“及格”;
    • score 小于 60 时,在界面上显示“不及格”;
  3. 我们有一个变量 message,记录一段文字,比如:"Hello World"
    • 某些情况下我们是直接显示这段文字;
    • 某些情况下我们需要对这段文字进行反转;

实现上述三个案例可以有三种思路:

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

1.1 实现思路一:模板语法

<body> 元素中的代码:

<div id="app"></div>

<template id="my-app">
  <h2>{{ firstName + ' ' + lastName }}</h2>
  <h2>{{ score >= 60 ? '及格' : '不及格' }}</h2>
  <h2>{{ message.split(' ').reverse().join(' ') }}</h2>
</template>

<script src="./js/vue.js"></script>
<script>
  const App = {
    data() {
      return {
        firstName: 'Shane',
        lastName: 'Filan',
        score: 80,
        message: 'Hello World'
      }
    },
    template: '#my-app'
  };

  Vue.createApp(App).mount('#app');
</script>

界面显示:

image-20210821105400624.png

可见,模板语法可以实现预期效果。但是存在以下三个缺点:

  1. 模板中如果存在大量复杂逻辑,不便于维护(模板中使用表达式的初衷是用于简单的计算);
  2. 当有多次一样的逻辑时,会存在重复代码;
  3. 多次使用时,很多运算也需要多次执行,没有缓存;

1.2 实现思路二:methods 实现

<body> 元素中的代码:

<div id="app"></div>

<template id="my-app">
  <h2>{{ getFullName() }}</h2>
  <h2>{{ getResult() }}</h2>
  <h2>{{ getReverseMessage() }}</h2>
</template>

<script src="./js/vue.js"></script>
<script>
  const App = {
    data() {
      return {
        firstName: 'Shane',
        lastName: 'Filan',
        score: 80,
        message: 'Hello World'
      }
    },
    methods: {
      getFullName() {
        return this.firstName + ' ' + this.lastName;
      },
      getResult() {
        return this.score >= 60 ? '及格' : '不及格';
      },
      getReverseMessage() {
        return this.message.split(' ').reverse().join(' ');
      }
    },
    template: '#my-app'
  };

  Vue.createApp(App).mount('#app');
</script>

通过在模板中调用 methods 选项中的方法,也可以实现预期效果。解决了思路一模板语法中存在大量复杂逻辑,不便于维护的问题以及当有多次一样的逻辑时,会存在重复代码的问题。但也存在以下两个缺点:

  1. 不够直观(我们想要显示的是一个结果,但现在却变成了方法的调用);
  2. 多次使用方法时,也没有缓存,方法会被多次调用,结果还是会进行多次重复计算;

1.3 实现思路三:computed 实现

<body> 元素中的代码:

<div id="app"></div>

<template id="my-app">
  <h2>{{ fullName }}</h2>
  <h2>{{ result }}</h2>
  <h2>{{ reverseMessage }}</h2>
</template>

<script src="./js/vue.js"></script>
<script>
  const App = {
    data() {
      return {
        firstName: 'Shane',
        lastName: 'Filan',
        score: 80,
        message: 'Hello World'
      }
    },
    computed: {
      // 定义了一个叫 fullName 的计算属性
      fullName: function() {
        return this.firstName + ' ' + this.lastName;
      },
      // 定义了一个叫 result 的计算属性
      result() {
        return this.score >= 60 ? '及格' : '不及格';
      },
      // 定义了一个叫 reverseMessage 的计算属性
      reverseMessage() {
        return this.message.split(' ').reverse().join(' ');
      }
    },
    template: '#my-app'
  };

  Vue.createApp(App).mount('#app');
</script>

通过在模板中使用 computed 选项中的计算属性,同样可以实现需求。相较于前面两种实现思路,使用计算属性有下面两个优势:

  • 直观上,计算属性更简洁、更优雅;
  • 并且,计算属性是有缓存的(一次执行,多次使用),因此性能上有很大提升。

注意:计算属性看起来像是一个函数,但实际上并不是函数,我们在使用它时后面是不加 () 的,这个后面讲 settergetter 时会讲到。