计算属性和侦听器

    我们想根据 是否已经有一些书来显示不同的消息

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

    此时,模板不再是简单的和声明性的。你必须先看一下它,然后才能意识到它执行的计算取决于 author.books。如果要在模板中多次包含此计算,则问题会变得更糟。

    所以,对于任何包含响应式数据的复杂逻辑,你都应该使用计算属性

    1. <div id="computed-basics">
    2. <p>Has published books:</p>
    3. <span>{{ publishedBooksMessage }}</span>
    4. </div>
    1. Vue.createApp({
    2. data() {
    3. return {
    4. author: {
    5. name: 'John Doe',
    6. books: [
    7. 'Vue 2 - Advanced Guide',
    8. 'Vue 3 - Basic Guide',
    9. 'Vue 4 - The Mystery'
    10. ]
    11. }
    12. }
    13. },
    14. computed: {
    15. // 计算属性的 getter
    16. publishedBooksMessage() {
    17. // `this` points to the vm instance
    18. return this.author.books.length > 0 ? 'Yes' : 'No'
    19. }
    20. }
    21. }).mount('#computed-basics')

    Result:

    这里声明了一个计算属性 publishedBooksMessage

    尝试更改应用程序 databooks 数组的值,你将看到 publishedBooksMessage 如何相应地更改。

    你可能已经注意到我们可以通过在表达式中调用方法来达到同样的效果:

    1. // 在组件中
    2. methods: {
    3. return this.author.books.length > 0 ? 'Yes' : 'No'
    4. }

    我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的反应依赖关系缓存的。计算属性只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 author.books 还没有发生改变,多次访问 publishedBookMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。

    这也同样意味着下面的计算属性将不再更新,因为 Date.now () 不是响应式依赖:

    1. computed: {
    2. now() {
    3. return Date.now()
    4. }
    5. }

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

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

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

    1. // ...
    2. computed: {
    3. fullName: {
    4. // getter
    5. get() {
    6. return this.firstName + ' ' + this.lastName
    7. },
    8. // setter
    9. set(newValue) {
    10. const names = newValue.split(' ')
    11. this.firstName = names[0]
    12. this.lastName = names[names.length - 1]
    13. }
    14. }
    15. }
    16. // ...

    侦听器

    虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

    例如:

    1. <!-- 因为 AJAX 库和通用工具的生态已经相当丰富,Vue 核心代码没有重复 -->
    2. <!-- 提供这些功能以保持精简。这也可以让你自由选择自己更熟悉的工具。 -->
    3. <script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
    4. <script>
    5. const watchExampleVM = Vue.createApp({
    6. data() {
    7. return {
    8. question: '',
    9. answer: 'Questions usually contain a question mark. ;-)'
    10. }
    11. watch: {
    12. // whenever question changes, this function will run
    13. if (newQuestion.indexOf('?') > -1) {
    14. this.getAnswer()
    15. }
    16. }
    17. },
    18. methods: {
    19. getAnswer() {
    20. this.answer = 'Thinking...'
    21. axios
    22. .get('https://yesno.wtf/api')
    23. .then(response => {
    24. this.answer = response.data.answer
    25. })
    26. .catch(error => {
    27. this.answer = 'Error! Could not reach the API. ' + error
    28. })
    29. }
    30. }
    31. }).mount('#watch-example')
    32. </script>

    结果:

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

    除了 watch 选项之外,你还可以使用命令式的 。

    Vue 提供了一种更通用的方式来观察和响应当前活动的实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性而不是命令式的 watch 回调。细想一下这个例子:

    1. <div id="demo">{{ fullName }}</div>
    1. const vm = Vue.createApp({
    2. data() {
    3. return {
    4. firstName: 'Foo',
    5. lastName: 'Bar',
    6. fullName: 'Foo Bar'
    7. }
    8. },
    9. watch: {
    10. firstName(val) {
    11. this.fullName = val + ' ' + this.lastName
    12. },
    13. lastName(val) {
    14. this.fullName = this.firstName + ' ' + val
    15. }

    好得多了,不是吗?