Computed Properties
In-template expressions are very convenient, but they are meant for simple operations. Putting too much logic in your templates can make them bloated and hard to maintain. For example, if we have an object with a nested array:
template内的表达式很方便,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让template十分臃肿(bloated)且难以维护,这里:如果有一个嵌套数组的对象:
Vue.createApp({
data() {
return {
author: {
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
}
}
}
})
And we want to display different messages depending on if author already has some books or not
然后,我们想根据作者有没有书来分别展示:
<div id="computed-basics">
<p>Has published books:</p>
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
</div>
At this point, the template is no longer simple and declarative. You have to look at it for a second before realizing that it performs a calculation depending on author.books. The problem is made worse when you want to include this calculation in your template more than once.
That's why for complex logic that includes reactive data, you should use a computed property
在这个地方,模板不再是简单的声明式逻辑。必须仔细看一下才能意识到,他是根据autor.books的计算结果来展示的。要是你想在template里在重复几次这个计算,那问题就更大了.
所以,当一些复杂逻辑包含响应式数据的时候,应该去用computed属性(property)。
Basic Example
<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: {
// a computed getter
publishedBooksMessage() {
// `this` points to the vm instance
return this.author.books.length > 0 ? 'Yes' : 'No'
}
}
}).mount('#computed-basics')
Here we have declared a computed property publishedBooksMessage.
这里声明了一个计算属性publishedBooksMessage.
Try to change the value of books array in the application data and you will see how publishedBooksMessage is changing accordingly.
去尝试改变books数组的值,就可以看到publishedBooksMessage会如何相应变化.
You can data-bind to computed properties in templates just like a normal property. Vue is aware that vm.publishedBooksMessage depends on vm.author.books, so it will update any bindings that depend on vm.publishedBooksMessage when vm.author.books changes. And the best part is that we've created this dependency relationship declaratively: the computed getter function has no side effects, which makes it easier to test and understand.
可以想绑定普通属性一样在template中绑定计算属性. Vue知道vm.publishedBooksMessage取决于vm.author.book,所以当vm.author.book值有变化的时候,Vue也会更新vm.publishedBooksMessage的相关绑定. 最棒的是:我们已经声明式地创建了这种依赖关系(vm.author.book和vm.publishedBooksMessage之间),所以计算属性的getter函数没有任何副作用(side effects)——这就很容易测试和理解了.
Computed Caching vs Methods 计算属性缓存 vs 方法
You may have noticed we can achieve the same result by invoking a method in the expression:
你可能已经发现,在表达式里调一个方法效果看起来时一样的:
<p>{{ calculateBooksMessage() }}</p>
// in component
methods: {
calculateBooksMessage() {
return this.author.books.length > 0 ? 'Yes' : 'No'
}
}
Instead of a computed property, we can define the same function as a method. For the end result, the two approaches are indeed exactly the same. However, the difference is that computed properties are cached based on their reactive dependencies. A computed property will only re-evaluate when some of its reactive dependencies have changed. This means as long as author.books has not changed, multiple access to the publishedBooksMessage computed property will immediately return the previously computed result without having to run the function again.
确实,这两种方式(用computed属性和用method)看起来是一模一样的.然而,区别是computed属性(properties)会基于他们的响应式依赖进行缓存. 也就是说,只有在他们的某些依赖关系值发生变化的时候computed属性才会重新计算. 在那个例子里,只要author.books值没变.多次访问publishedBooksMessage这个计算属性都会立刻返回之前的计算结果(不会去重新调用函数计算).
This also means the following computed property will never update, because Date.now() is not a reactive dependency:
但这也意味着,这种计算属性永远不会更新.因为Date.now()不是一个响应式依赖:
computed: {
now() {
return Date.now()
}
}
In comparison, a method invocation will always run the function whenever a re-render happens.
Why do we need caching? Imagine we have an expensive computed property list, which requires looping through a huge array and doing a lot of computations. Then we may have other computed properties that in turn depend on list. Without caching, we would be executing list’s getter many more times than necessary! In cases where you do not want caching, use a method instead.
相对的, 重新渲染的时候,method总是会再次执行的.
我们为啥要缓存.想一下,有一个computed属性运算开销很大的list值——在一个庞大的数组里循环多次,做了很多运算. 然后你有另一个computed属性依赖于这个list. 不缓存的话,list的getter函数会白运行几次.如果真的不想缓存,那就去使用method代替.
Computed Setter
Computed properties are by default getter-only, but you can also provide a setter when you need it:
默认computed属性只有getter函数,但是你可以提供一个setter函数
// ...
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]
}
}
}
// ...
Now when you run vm.fullName = 'John Doe', the setter will be invoked and vm.firstName and vm.lastName will be updated accordingly.
现在,当运行了vm.fullName = 'John Doe'这条语句时, 就会触发setter函数,vm.firstName 和 vm.lastName就相应更新啦.
Watchers
While computed properties are more appropriate in most cases, there are times when a custom watcher is necessary. That's why Vue provides a more generic way to react to data changes through the watch option. This is most useful when you want to perform asynchronous or expensive operations in response to changing data.
虽然computed属性大部分情况下都很合适,也有需要自定义一个watcher的时候. Vue提供了一个更通用的方式去相应data的变化——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>
const watchExampleVM = Vue.createApp({
data() {
return {
question: '',
answer: 'Questions usually contain a question mark. ;-)'
}
},
watch: {
// whenever question changes, this function will run
question(newQuestion, oldQuestion) {
if (newQuestion.indexOf('?') > -1) {
this.getAnswer()
}
}
},
methods: {
getAnswer() {
this.answer = 'Thinking...'
axios
.get('https://yesno.wtf/api')
.then(response => {
this.answer = response.data.answer
})
.catch(error => {
this.answer = 'Error! Could not reach the API. ' + error
})
}
}
}).mount('#watch-example')
</script>
In this case, using the watch option allows us to perform an asynchronous operation (accessing an API) and sets a condition for performing this operation. None of that would be possible with a computed property.
In addition to the watch option, you can also use the imperative vm.$watch API.
这这里,使用watch可以让我们能够执行一个异步操作(访问了一个API),设置了一个判断条件.这些操作在computed属性里都没办法完成.
除了watch选项使用之外,你还可以指令式(imperative)的*使用vm.$watch API.
Computed vs Watched Property 两者比较
Vue does provide a more generic way to observe and react to data changes on a current active instance: watch properties. When you have some data that needs to change based on some other data, it is tempting to overuse watch - especially if you are coming from an AngularJS background. However, it is often a better idea to use a computed property rather than an imperative watch callback. Consider this example:
Vue确实提供了一种观察和响应 应用实例上data变换的 通用方法——watch属性. 当你有些需要依赖其他data变化而变化的data时, **滥用watch**是很有诱惑的(特别是如果你有AngularJS背景). 但是 往往跟指令式watch回调相比,使用computed方法是一个更好的选择.考虑一下:
<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')
The above code is imperative and repetitive. Compare it with a computed property version:
上面这代码是命令式并且重复的(😵).跟computed比一下
<div id="demo">{{ fullName }}</div>
const vm = Vue.createApp({
data() {
return {
firstName: 'Foo',
lastName: 'Bar',
}
},
computed:{
fullName(){
return `${this.firstName} ${this.lastName}`
}
}
}
}).mount('#demo')
Much better, isn't it?
好多了吧?是吧~是的.