Skip to content

Commit aaa20db

Browse files
committed
computed
1 parent d43fd69 commit aaa20db

File tree

2 files changed

+226
-130
lines changed

2 files changed

+226
-130
lines changed

src/guide/essentials/computed.md

Lines changed: 114 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -39,28 +39,21 @@ const author = ref({
3939

4040
</div>
4141

42-
And we want to display different messages depending on if `author` already has some books or not
42+
And we want to display different messages depending on if `author` already has some books or not:
4343

4444
```vue-html
45-
<div id="computed-basics">
46-
<p>Has published books:</p>
47-
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
48-
</div>
45+
<p>Has published books:</p>
46+
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
4947
```
5048

51-
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.
49+
At this point, the template is getting a bit cluttered. We have to look at it for a second before realizing that it performs a calculation depending on `author.books`. More importantly, we probably don't want to repeat ourselves if we need to include this calculation in the template more than once.
5250

53-
That's why for complex logic that includes reactive data, you should use a **computed property**. Here's the same example, refactored:
51+
That's why for complex logic that includes reactive data, it is recommended to use a **computed property**. Here's the same example, refactored:
5452

55-
```vue-html
56-
<div id="computed-basics">
57-
<p>Has published books:</p>
58-
<span>{{ publishedBooksMessage }}</span>
59-
</div>
60-
```
53+
<div class="options-api">
6154

6255
```js
63-
Vue.createApp({
56+
export default {
6457
data() {
6558
return {
6659
author: {
@@ -80,18 +73,58 @@ Vue.createApp({
8073
return this.author.books.length > 0 ? 'Yes' : 'No'
8174
}
8275
}
83-
}).mount('#computed-basics')
76+
}
8477
```
8578

86-
Result:
79+
```vue-html
80+
<p>Has published books:</p>
81+
<span>{{ publishedBooksMessage }}</span>
82+
```
8783

88-
<!-- <common-codepen-snippet title="Computed basic example" slug="NWqzrjr" tab="js,result" :preview="false" /> -->
84+
[Try it in the Playground](https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdD5cbmV4cG9ydCBkZWZhdWx0IHtcbiAgZGF0YSgpIHtcbiAgICByZXR1cm4ge1xuICAgICAgYXV0aG9yOiB7XG4gICAgICAgIG5hbWU6ICdKb2huIERvZScsXG4gICAgICAgIGJvb2tzOiBbXG4gICAgICAgICAgJ1Z1ZSAyIC0gQWR2YW5jZWQgR3VpZGUnLFxuICAgICAgICAgICdWdWUgMyAtIEJhc2ljIEd1aWRlJyxcbiAgICAgICAgICAnVnVlIDQgLSBUaGUgTXlzdGVyeSdcbiAgICAgICAgXVxuICAgICAgfVxuICAgIH1cbiAgfSxcbiAgY29tcHV0ZWQ6IHtcbiAgICBwdWJsaXNoZWRCb29rc01lc3NhZ2UoKSB7XG4gICAgICByZXR1cm4gdGhpcy5hdXRob3IuYm9va3MubGVuZ3RoID4gMCA/ICdZZXMnIDogJ05vJ1xuICAgIH1cbiAgfVxufVxuPC9zY3JpcHQ+XG5cbjx0ZW1wbGF0ZT5cbiAgPHA+SGFzIHB1Ymxpc2hlZCBib29rczo8L3A+XG4gIDxzcGFuPnt7IGF1dGhvci5ib29rcy5sZW5ndGggPiAwID8gJ1llcycgOiAnTm8nIH19PC9zcGFuPlxuPC90ZW1wbGF0ZT4iLCJpbXBvcnQtbWFwLmpzb24iOiJ7XG4gIFwiaW1wb3J0c1wiOiB7XG4gICAgXCJ2dWVcIjogXCJodHRwczovL3NmYy52dWVqcy5vcmcvdnVlLnJ1bnRpbWUuZXNtLWJyb3dzZXIuanNcIlxuICB9XG59In0=)
8985

9086
Here we have declared a computed property `publishedBooksMessage`.
9187

9288
Try to change the value of `books` array in the application `data` and you will see how `publishedBooksMessage` is changing accordingly.
9389

94-
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.
90+
You can data-bind to computed properties in templates just like a normal property. Vue is aware that `this.publishedBooksMessage` depends on `this.author.books`, so it will update any bindings that depend on `vm.publishedBooksMessage` when `this.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.
91+
92+
</div>
93+
94+
<div class="composition-api">
95+
96+
```vue
97+
<script setup>
98+
import { ref, computed } from 'vue'
99+
100+
const author = ref({
101+
name: 'John Doe',
102+
books: [
103+
'Vue 2 - Advanced Guide',
104+
'Vue 3 - Basic Guide',
105+
'Vue 4 - The Mystery'
106+
]
107+
})
108+
109+
// a computed ref
110+
const publishedBooksMessage = computed(() => {
111+
return author.value.books.length > 0 ? 'Yes' : 'No'
112+
})
113+
</script>
114+
115+
<template>
116+
<p>Has published books:</p>
117+
<span>{{ publishedBooksMessage }}</span>
118+
</template>
119+
```
120+
121+
[Try it in the Playground](https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdCBzZXR1cD5cbmltcG9ydCB7IHJlZiwgY29tcHV0ZWQgfSBmcm9tICd2dWUnXG5cbmNvbnN0IGF1dGhvciA9IHJlZih7XG4gIG5hbWU6ICdKb2huIERvZScsXG4gIGJvb2tzOiBbXG4gICAgJ1Z1ZSAyIC0gQWR2YW5jZWQgR3VpZGUnLFxuICAgICdWdWUgMyAtIEJhc2ljIEd1aWRlJyxcbiAgICAnVnVlIDQgLSBUaGUgTXlzdGVyeSdcbiAgXVxufSlcblxuLy8gYSBjb21wdXRlZCByZWZcbmNvbnN0IHB1Ymxpc2hlZEJvb2tzTWVzc2FnZSA9IGNvbXB1dGVkKCgpID0+IHtcbiAgcmV0dXJuIGF1dGhvci52YWx1ZS5ib29rcy5sZW5ndGggPiAwID8gJ1llcycgOiAnTm8nXG59KVxuPC9zY3JpcHQ+XG5cbjx0ZW1wbGF0ZT5cbiAgPHA+SGFzIHB1Ymxpc2hlZCBib29rczo8L3A+XG4gIDxzcGFuPnt7IHB1Ymxpc2hlZEJvb2tzTWVzc2FnZSB9fTwvc3Bhbj5cbjwvdGVtcGxhdGU+IiwiaW1wb3J0LW1hcC5qc29uIjoie1xuICBcImltcG9ydHNcIjoge1xuICAgIFwidnVlXCI6IFwiaHR0cHM6Ly9zZmMudnVlanMub3JnL3Z1ZS5ydW50aW1lLmVzbS1icm93c2VyLmpzXCJcbiAgfVxufSJ9)
122+
123+
Here we have declared a computed property `publishedBooksMessage`. The `computed()` method expects a getter function, and returned value is a **computed ref**. Similar to normal refs, you can access the computed result as `publishedBooksMessage.value`. Computed refs are also auto-unwrapped in templates so you can reference them without `.value` in template expressions.
124+
125+
Vue is aware that the computation of `publishedBooksMessage` depends on `author.value.books`, so it will update any bindings that depend on `publishedBooksMessage` when `author.value.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.
126+
127+
</div>
95128

96129
## Computed Caching vs Methods
97130

@@ -101,6 +134,8 @@ You may have noticed we can achieve the same result by invoking a method in the
101134
<p>{{ calculateBooksMessage() }}</p>
102135
```
103136

137+
<div class="options-api">
138+
104139
```js
105140
// in component
106141
methods: {
@@ -110,154 +145,104 @@ methods: {
110145
}
111146
```
112147

113-
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.
148+
</div>
114149

115-
This also means the following computed property will never update, because `Date.now()` is not a reactive dependency:
150+
<div class="composition-api">
116151

117152
```js
118-
computed: {
119-
now() {
120-
return Date.now()
121-
}
153+
// in component
154+
function calculateBooksMessage() {
155+
return author.value.books.length > 0 ? 'Yes' : 'No'
122156
}
123157
```
124158

125-
In comparison, a method invocation will **always** run the function whenever a re-render happens.
159+
</div>
126160

127-
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.
161+
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 <code class="options-api">author.books</code><code class="composition-api">author.value.books</code> has not changed, multiple access to `publishedBooksMessage` will immediately return the previously computed result without having to run the getter function again.
128162

129-
## Writable Computed
163+
This also means the following computed property will never update, because `Date.now()` is not a reactive dependency:
130164

131-
Computed properties are by default getter-only, but you can also provide a setter when you need it:
165+
<div class="options-api">
132166

133167
```js
134-
// ...
135168
computed: {
136-
fullName: {
137-
// getter
138-
get() {
139-
return this.firstName + ' ' + this.lastName
140-
},
141-
// setter
142-
set(newValue) {
143-
const names = newValue.split(' ')
144-
this.firstName = names[0]
145-
this.lastName = names[names.length - 1]
146-
}
169+
now() {
170+
return Date.now()
147171
}
148172
}
149-
// ...
150173
```
151174

152-
Now when you run `vm.fullName = 'John Doe'`, the setter will be invoked and `vm.firstName` and `vm.lastName` will be updated accordingly.
153-
154-
## Watchers
155-
156-
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.
157-
158-
For example:
159-
160-
```vue-html
161-
<div id="watch-example">
162-
<p>
163-
Ask a yes/no question:
164-
<input v-model="question" />
165-
</p>
166-
<p>{{ answer }}</p>
167175
</div>
168-
```
169176

170-
```vue-html
171-
<!-- Since there is already a rich ecosystem of ajax libraries -->
172-
<!-- and collections of general-purpose utility methods, Vue core -->
173-
<!-- is able to remain small by not reinventing them. This also -->
174-
<!-- gives you the freedom to use what you're familiar with. -->
175-
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js"></script>
176-
<script>
177-
const watchExampleVM = Vue.createApp({
178-
data() {
179-
return {
180-
question: '',
181-
answer: 'Questions usually contain a question mark. ;-)'
182-
}
183-
},
184-
watch: {
185-
// whenever question changes, this function will run
186-
question(newQuestion, oldQuestion) {
187-
if (newQuestion.indexOf('?') > -1) {
188-
this.getAnswer()
189-
}
190-
}
191-
},
192-
methods: {
193-
getAnswer() {
194-
this.answer = 'Thinking...'
195-
axios
196-
.get('https://yesno.wtf/api')
197-
.then(response => {
198-
this.answer = response.data.answer
199-
})
200-
.catch(error => {
201-
this.answer = 'Error! Could not reach the API. ' + error
202-
})
203-
}
204-
}
205-
}).mount('#watch-example')
206-
</script>
207-
```
177+
<div class="composition-api">
208178

209-
Result:
179+
```js
180+
const now = computed(() => Date.now())
181+
```
210182

211-
<!-- <common-codepen-snippet title="Watch basic example" slug="GRJGqXp" tab="result" :preview="false" /> -->
183+
</div>
212184

213-
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.
185+
In comparison, a method invocation will **always** run the function whenever a re-render happens.
214186

215-
In addition to the `watch` option, you can also use the imperative [vm.$watch API](/api/component-instance.html#watch).
187+
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 call instead.
216188

217-
### Computed vs Watched Property
189+
## Writable Computed
218190

219-
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:
191+
Computed properties are by default getter-only. When you attempt to mutate a computed property, you will receive a runtime warning. But you can provide a setter to create a "writable" computed property if you need to:
220192

221-
```vue-html
222-
<div id="demo">{{ fullName }}</div>
223-
```
193+
<div class="options-api">
224194

225195
```js
226-
const vm = Vue.createApp({
196+
export default {
227197
data() {
228198
return {
229-
firstName: 'Foo',
230-
lastName: 'Bar',
231-
fullName: 'Foo Bar'
199+
firstName: 'John',
200+
lastName: 'Doe'
232201
}
233202
},
234-
watch: {
235-
firstName(val) {
236-
this.fullName = val + ' ' + this.lastName
237-
},
238-
lastName(val) {
239-
this.fullName = this.firstName + ' ' + val
203+
computed: {
204+
fullName: {
205+
// getter
206+
get() {
207+
return this.firstName + ' ' + this.lastName
208+
},
209+
// setter
210+
set(newValue) {
211+
// Note: we are using destructuring assignment syntax here.
212+
;[this.firstName, this.lastName] = newValue.split(' ')
213+
}
240214
}
241215
}
242-
}).mount('#demo')
216+
}
243217
```
244218

245-
The above code is imperative and repetitive. Compare it with a computed property version:
219+
Now when you run `this.fullName = 'John Doe'`, the setter will be invoked and `this.firstName` and `this.lastName` will be updated accordingly.
246220

247-
```js
248-
const vm = Vue.createApp({
249-
data() {
250-
return {
251-
firstName: 'Foo',
252-
lastName: 'Bar'
253-
}
221+
</div>
222+
223+
<div class="composition-api">
224+
225+
```vue
226+
<script setup>
227+
import { ref, computed } from 'vue'
228+
229+
const firstName = ref('John')
230+
const lastName = ref('Doe')
231+
232+
const fullName = computed({
233+
// getter
234+
get() {
235+
return firstName.value + ' ' + lastName.value
254236
},
255-
computed: {
256-
fullName() {
257-
return this.firstName + ' ' + this.lastName
258-
}
237+
// setter
238+
set(newValue) {
239+
// Note: we are using destructuring assignment syntax here.
240+
;[firstName.value, lastName.value] = newValue.split(' ')
259241
}
260-
}).mount('#demo')
242+
})
243+
</script>
261244
```
262245

263-
Much better, isn't it?
246+
Now when you run `fullName.value = 'John Doe'`, the setter will be invoked and `firstName` and `lastName` will be updated accordingly.
247+
248+
</div>

0 commit comments

Comments
 (0)