天天看点

Vue | 05 计算属性和监听器

内容提要:
  1. 计算属性的使用场景及表示方法
  2. 计算缓存与方法的差异
  3. 计算属性与监听器属性的使用比较
  4. 计算属性的Setter表示方法
  5. 监听器的使用方式

计算属性

在模板内写表达式是非常方便的,但它们仅仅能做一些简单的操作,把太复杂的表达式放在模板中可能会导致臃肿和难以维护。例如:

<div id="example">
    {{ message.split('').reverse().join('') }}
</div>
           

如上,模板不再是简单的和说明式的。在你读懂它是

message

的字符串反转之前你不得不读几秒钟。当这样的语法不止一次的出现,情况就会变得很糟糕。

这就是为什么对于复杂一点的逻辑你需要使用计算属性的原因。

基础的例子

<div id="example">
    <p>Original message:"{{ message }}"</p>
    <p>Computed reversed message:"{{ reversedMessage }}</p>
</div>
           
var vm = new Vue({
    el: '#example',
    data: {
        message: 'Hello'
    },
    computed: {
        // a computed getter
        reversedMessage: function() {
            // 'this'points to the vm instance
            return this.message.split('').reverse().join('')
        }
    }
})
           

Result:

Original message: "Hello"<br>
Computed reversed message: "olleH"
           

这里声明了一个计算属性

reversedMessage

.我们为属性

vm.reversedMessage

提供了一个getter函数:

console.log(vm.reversedMessage) // => 'olleH'
vm.message = 'Goodbye'
console.log(vm.reversedMessage) // => 'eybdooG'
           

vm.reversedMessage

的值依赖于

vm.message

.

我们可以像绑定正常的函数一样绑定计算属性。Vue能够意识到

vm.reversedMessage

依赖

vm.message

,所以当

vm.message

更新的时候它能够更新所有

vm.reversedMessage

的绑定。因为我们已经创建了他们的依赖关系:计算getter函数没有副作用,它更容易测试和理解。

计算缓存 vs 方法

你可能意识到了可以通过一个方法表达式得到同样的结果。

// in component
methods: {
    reverseMessage: function() {
        return this.message.split('').reverse().join('')
    }
}
           

我们定义了一个相同的函数作为方法来代替计算属性。作为结果,这两种实现是相同的。然而,他们的不同在于计算属性缓存了他们的依赖关系。当依赖关系改变的时候计算属性才会被重新计算。这意味着只要

message

没有改变,多次访问reversedMessage计算属性将立刻返回之前的结果而不再运行函数。

这也意味着以下属性从不会更新,因为

Date.now()

没有一个可响应的依赖。

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

相比之下,无论是否重新渲染一个方法调用总是会运行函数。

我们为什么需要缓存?想象一下我们有非常耗时的计算属性A,要求遍历一个很大的数组并且做很多的计算。而后我们有其他计算属性反过来依赖A。没有缓存,我们将比实际需要执行A的getter很多次!某些情况下,如果你不需要缓存,就用一个方法代替。

计算 vs 监听器属性

Vue提供了一个更为常用的方式去观察和响应数据的改变在Vue实例:watch properties。当一些数据需要基于一些其他数据做改变的时候,过度使用

watch

是很诱人的 - 尤其是如果你有一个AngularJS背景。然而,通常使用计算属性要比强制回调

watch

要好。考虑这个例子:

<div id="demo">
    {{ fullName }}
</div>
           
var vm = new Vue({
    el: '#demo',
    data: {
        firstName: 'Foo',
        lastName: 'Bar',
        fullName: 'Foo Bar'
    },
    watch: {
        firstName: function (val) {
          this.fullName = val + ' ' + this.lastName  
        },  
        lastName: function (val) {
          this.fullName = this.firstName + ' ' + val
        }
    },
})
           

上面的代码是强制的和重复的。比较一下计算属性版本:

var vm = new Vue({
    el: '#demo',
    data: {
        firtName: 'Foo',
        lastName: 'Bar'
    },
    computed: {
        fullName: function () {
            return this.firstName + ' ' + this.lastName
       }
    }
})
           

显然,第二个要好一些。

计算Setter

计算属性默认仅为getter,但是当你需要的时候你也能提供一个setter:

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

现在当你运行

vm.fullName = 'John Doe'

,setter将被调用,

vm.firstName

vm.lastName

将会被更新。

监听器

虽然计算属性在大多数情况下更合适,有时仍需要一个自定义的监听器。这就是为什么Vue提供了更通用的方式去响应数据的变化通过

watch

选项。当你执行异步或耗时操作去响应数据改变的时候会更有用。

例如:

<div id="watch-example">
    <p>Ask a yes/no question:
    <input v-model="question">
    </p>
    <p>{{ answer }}</p>
</div>
           
<!-- Since there is already a rich ecosystem of ajax libraris -->
<!-- and collections of general-purpose utility methods, Vue core -->
<!-- is able to remain small by not reinventing them. This also -->
<!-- gives you the freedom to use what you're familiar with.' -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js"></script>  
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
<script>
var watchExampleVM = new Vue({
    el: '#watch-example',
    data: {
        question: '',
        answer: 'I cannot give you an answer until you ask a question!'
    },
    watch: {
        // whenever question changes, this function will run
        question: function (newQuestion, oldQuestion) {
            this.answer = 'Waiting for you to stop typing...'
            this.debouncedGetAnswer()
        }
    },
    created: function () {
        // _.debounce is a funciton provided by lodash to limit how often a particularly expensive operation can be run.In this case, we want to limit how often we access yesno.wtf/api, waiting until the user has completely finished typing before making the ajax request. To learn more about the _.debounce function (and its cousin _.throttle), visit: https://lodash.com/docs#debounce
        this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
    },
    methods: {
        getAnswer: function () {
            if (this.question.indexOf('?') === -1) {
                this.answer = 'Questions usually contain a question mark. ;-)'
                return
            }
            this.answer = 'Thinking...'
            var vm = this
            axios.get('https:// yesno.wtf/api')
                .then(function (response)) {
                      vm.answer = _.capitalize(response.data.answer)
                      })
        .catch(function (error)) {
    			vm.answer = 'Error! Could not reach the API. ' + error
			})
        }
    }
})
</script>
           

Result:

问题输入改变时,文字提示会实时改为:Waiting for you to stop typing …

问题不规范:

Vue | 05 计算属性和监听器

请求中:

Vue | 05 计算属性和监听器

请求回来:

Vue | 05 计算属性和监听器

在这个例子里,使用

watch

操作允许我们执行一个异步的操作(访问一个API的时候),限制我们操作的频率,当得到最后的回答时立刻改变状态。用计算属性是做不到的。

除了

watch

选项,你也可以使用命令

vm.$watch API

.

继续阅读