5. Computed Properties and Watchers(计算属性和观察者)
计算属性和Watchers
计算属性
模板内表达式非常方便,但它们适用于简单的操作。在你的模板中加入太多的逻辑会使它们变得臃肿,难以维护。例如:
<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('')
}
}
})
结果:
这里我们已经声明了一个计算属性reversedMessage
。我们提供的函数将用作该属性的getter函数vm.reversedMessage
:
console.log(vm.reversedMessage) // => 'olleH'
vm.message = 'Goodbye'
console.log(vm.reversedMessage) // => 'eybdooG'
您可以打开控制台并自行演示vm例子。值vm.reversedMessage
总是取决于值vm.message
。
您可以将数据绑定到模板中的计算属性,就像普通属性一样。Vue作者意识到,vm.reversedMessage
取决于vm.message
,所以它会更新依赖于任何绑定vm.reversedMessage
时vm.message
的变化。最好的部分是我们已经声明性地创建了这个依赖关系:计算得到的getter函数没有副作用,这使得它更易于测试和理解。
计算缓存及方法
您可能已经注意到我们可以通过调用表达式中的方法来实现相同的结果:
<p>Reversed message: "{{ reverseMessage() }}"</p>
// 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超过必要的次数!如果您不想缓存,请改用方法。
计算与看过的属性
Vue确实提供了一种更通用的方式来观察和响应Vue实例上的数据更改:监视属性
。当你有一些数据需要根据其他数据进行更改时,过度使用会很诱人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: {
firstName: '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
会相应地更新。
Watchers
尽管在大多数情况下计算出的属性更合适,但有时需要定制观察者。这就是为什么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 libraries -->
<!-- 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/axios@0.12.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/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) {
this.answer = 'Waiting for you to stop typing...'
this.getAnswer()
}
},
methods: {
// _.debounce is a function 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
getAnswer: _.debounce(
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
})
},
// This is the number of milliseconds we wait for the
// user to stop typing.
500
)
}
})
</script>
结果:
在这种情况下,使用该watch
选项允许我们执行异步操作(访问API),限制我们执行该操作的频率,并设置中间状态,直到获得最终答案。计算出来的属性都是不实际的。
除了这个watch
选项外,你还可以使用命令式的vm.$ watch
API。