计算属性和侦听器

阅读: 5621     评论:0

计算属性computed

看下面的例子,假设有一个嵌套数组对象author:

Vue.createApp({
  data() {
    return {
      author: {
        name: 'John Doe',
        books: [
          'Vue 2 - Advanced Guide',
          'Vue 3 - Basic Guide',
          'Vue 4 - The Mystery'
        ]
      }
    }
  }
})

需求是,如果该作者出过书,则显示yes,否则显示no。我们可以在HTML的span标签中插入JS表达式来实现:

<div id="computed-basics">
  <p>Has published books:</p>
  <span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
</div>

这个三元表达式不是简单的和声明性的(尽管相对写个方法,写个类,这起码还是一条语句。)

我们必须先看一下它,然后才能意识到它执行的操作取决于 author.books

如果要在模板中多次进行这个判断,问题会变得更糟。

Vue的设计理念强调,对于任何包含响应式数据的复杂逻辑,你都应该使用计算属性来实现,而不是将它嵌入到模板中(实际上99%的业务逻辑,你在模板中也写不下)。

正确的做法

<div id="computed-basics">
  <p>Has published books:</p>
  <span>{{ publishedBooksMessage }}</span>
</div>
Vue.createApp({
  data() {
    return {
      author: {
        name: 'John Doe',
        books: [
          'Vue 2 - Advanced Guide',
          'Vue 3 - Basic Guide',
          'Vue 4 - The Mystery'
        ]
      }
    }
  },
  computed: {
    // 计算属性的 getter
    publishedBooksMessage() {
      // `this` 指向 vm 实例
      return this.author.books.length > 0 ? 'Yes' : 'No'
    }
  }
}).mount('#computed-basics')

代码还是那句代码,不过我们将它放到computed选项卡里,以一个属性的形式存在。

所谓的计算,就是指这个属性不是直接可以得到的,而是需要通过执行一些计算代码,转换出来的。

我们可以尝试清空 databooks 数组的值,你将看到 publishedBooksMessage 属性会相应地更改。

每个计算属性都类似data选项卡中的属性,会自动绑定到应用实例上。

并且,Vue 知道 vm.publishedBookMessage 依赖于 vm.author.books,因此当 vm.author.books 发生改变时,所有依赖 vm.publishedBookMessage 的绑定也会更新。

也就是说,计算属性不但是响应式的,并且每当它的依赖发生了变化,它本身也会跟着重新计算!

计算属性缓存 vs 方法

实际上,对于上面的例子,我们可以写一个method实现同样的效果:

<p>{{ calculateBooksMessage() }}</p>
// 在组件中
methods: {
  calculateBooksMessage() {
    return this.author.books.length > 0 ? 'Yes' : 'No'
  }
}

我们可以将同样的操作,定义为一个方法,而不是一个计算属性,两种方式的最终结果是完全相同的。

然而,不同的是计算属性是基于它们的响应依赖关系缓存的。计算属性只在相关响应式依赖发生改变时它们才会重新求值。

这就意味着只要 author.books 还没有发生改变,多次访问 publishedBookMessage 计算属性会立即返回之前的计算结果,而不会再次执行函数。

这也意味着下面的计算属性将不再更新,因为 Date.now () 不是响应式的数据(也就是说不是Vue管理起来的数据,响应能力是Vue提供的):

computed: {
  now() {
    return Date.now()
  }
}

相比之下,每当触发重新渲染时,method方法将总会再次执行。

两者的对比总结:

  • 计算属性可以有依赖,也可以没有
  • 计算属性会跟随响应式依赖进行响应,对于非响应式数据无动于衷
  • 计算属性会缓存,依赖没发生变化,就不重新执行代码
  • method方法没有缓存,也没有依赖的概念,你调用它一次,它就重新执行一次
  • 两者没有好坏之分,只有适用场景的区别

我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 list,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 list。如果没有缓存,我们将不可避免的多次执行 list 的 getter!如果你不希望有缓存,请用 method 来替代。

计算属性的 Setter

getter:将计算属性计算出来的代码

setter:为计算属性赋值的代码

计算属性默认只有 getter,不过在你需要时也可以提供一个 setter,方法如下:

const cc={
    data(){
        return {
            firstName:'',
            lastName:''
        }
    }
}

// ...
computed: {
  fullName: {
    // getter
    get() {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set(newValue) {
      const names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}
// ...

有了setter,我们就可以执行类似 vm.fullName = 'John Doe' 的操作,这时,setter 会被调用,vm.firstNamevm.lastName 也会相应地被更新。

侦听器watch

计算属性一般是对某个响应式数据进行加工处理获得新数据。比如计算出学生中男生的个数。

而另外一类需求是,监视某个响应式数据,如果它发生变化,就自动调用某个函数。比如,当学生中男生的人数增加时,自动添加对应人数的女生。

Vue通过watch侦听器来实现这类需求。

并且当需要在数据变化时执行异步或开销较大的操作时,watch是最有用的。

例如:

<div id="watch-example">
  <p>    这是一个只会回答yes或者no的傻瓜例子:     </p>  
<input v-model="question" /> 
<p>{{ answer }}</p>
</div>
<!-- 因为 AJAX 库和通用工具的生态已经相当丰富,Vue 核心代码没有重复 -->
<!-- 提供这些功能以保持精简。这也可以让你自由选择自己更熟悉的工具。 -->
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script>
  const watchExampleVM = Vue.createApp({
    data() {
      return {
        question: '',
        answer: '请以问号结尾;-)'
      }
    },
    watch: {
      // whenever question changes, this function will run
      question(newQuestion, oldQuestion) {
        if (newQuestion.indexOf('?') > -1) {
          this.getAnswer()
        }
      }
    },
    methods: {
      getAnswer() {
        this.answer = '思考中...'
        axios
          .get('https://yesno.wtf/api')
          .then(response => {
            this.answer = response.data.answer
          })
          .catch(error => {
            this.answer = '对不起!无法访问远程API. ' + error
          })
      }
    }
  }).mount('#watch-example')
</script>

测试的时候,要使用英文问号结尾哦!

上面的例子中,使用 watch 选项允许我们执行异步操作 (比如访问一个远程的 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

计算属性 vs 侦听器

侦听器有不少有点,但它一定就比计算属性好吗?认真思考一下这个例子:

<div id="demo">{{ fullName }}</div>
const vm = Vue.createApp({
  data() {
    return {
      firstName: 'Foo',
      lastName: 'Bar',
      fullName: 'Foo Bar'
    }
  },
  watch: {
    firstName(val) {
      this.fullName = val + ' ' + this.lastName
    },
    lastName(val) {
      this.fullName = this.firstName + ' ' + val
    }
  }
}).mount('#demo')

上面代码是命令式且重复的。将它与计算属性的版本进行比较:

const vm = Vue.createApp({
  data() {
    return {
      firstName: 'Foo',
      lastName: 'Bar'
    }
  },
  computed: {
    fullName() {
      return this.firstName + ' ' + this.lastName
    }
  }
}).mount('#demo')

还是那句话,没有绝对意义的好坏优劣,只有适用场景的不同。


 数据和方法 类和样式 

评论总数: 0


点击登录后方可评论