Skip to content

Commit

Permalink
computed stability optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Dec 29, 2023
1 parent d69ec67 commit 5e486e4
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 3 deletions.
5 changes: 3 additions & 2 deletions src/api/reactivity-core.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ Takes a getter function and returns a readonly reactive [ref](#ref) object for t
```ts
// read-only
function computed<T>(
getter: () => T,
getter: (oldValue: T | undefined) => T,
// see "Computed Debugging" link below
debuggerOptions?: DebuggerOptions
): Readonly<Ref<Readonly<T>>>

// writable
function computed<T>(
options: {
get: () => T
get: (oldValue: T | undefined) => T
set: (value: T) => void
},
debuggerOptions?: DebuggerOptions
Expand Down Expand Up @@ -112,6 +112,7 @@ Takes a getter function and returns a readonly reactive [ref](#ref) object for t
- [Guide - Computed Properties](/guide/essentials/computed)
- [Guide - Computed Debugging](/guide/extras/reactivity-in-depth#computed-debugging)
- [Guide - Typing `computed()`](/guide/typescript/composition-api#typing-computed) <sup class="vt-badge ts" />
- [Guide - Performance - Computed Stability](/guide/best-practices/performance#computed-stability) <sup class="vt-badge" data-text="3.4+" />

## reactive() {#reactive}

Expand Down
47 changes: 47 additions & 0 deletions src/guide/best-practices/performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,53 @@ Now, for most components the `active` prop will remain the same when `activeId`

`v-memo` is a built-in directive that can be used to conditionally skip the update of large sub-trees or `v-for` lists. Consult its [API reference](/api/built-in-directives#v-memo) for more details.

### Computed Stability <sup class="vt-badge" data-text="3.4+" /> {#computed-stability}

Starting in 3.4, a computed property will only trigger effects when its computed value has changed from the previous one. For example, the following `isEven` computed only triggers effects if the returned value has changed from `true` to `false`, or vice-versa:

```js
const count = ref(0)
const isEven = computed(() => {
return count.value % 2 === 0 ? true : false
})

watchEffect(() => console.log(isEven.value)) // true

// will not trigger new logs because the computed value stays `true`
count.value = 2
count.value = 4
```

This reduces unnecessary effect triggers, but unfortunately doesn't work if the computed creates a new object on each compute:

```js
const computedObj = computed(() => {
return {
isEven: count.value % 2 === 0 ? true : false
}
})
```

Because a new object is created each time, the new value is technically always different from the old value. Even if the `isEven` property remains the same, Vue won't be able to know unless it performs a deep comparison of the old value and the new value. Such comparison could be expensive and likely not worth it.

Instead, we can optimize this by manually comparing the new value with the old value, and conditionally returning the old value if we know nothing has changed:

```js
const computedObj = computed(oldValue => {
const newValue = {
isEven: count.value % 2 === 0 ? true : false
}
if (oldValue && oldValue.isEven === newValue.isEven) {
return oldValue
}
return newValue
})
```

[Playground Example](https://play.vuejs.org/#eNqlVMlu2zAQ/ZUBgSZ24UpuczMkd4MPLdAFadEeyh4UibKVSKRADm2jhv+9Q1KyDTRRXOQggJrlzeO8Ge7Y27aN1lawGUtMrqsWwQi07ZzLqmmVRtiBFuUEctW0FkUxgU2G+WpRliJH2EOpVQOXhHDJJZe5kgYp1kqE1CWOpuOjXfikayvNzwpXyuKXFqum+pNhpWQX/+s3JfQwoeT9wb13pOriR1ZbAekcdlwCwaDVMpwBKrNYCzkLpKK1j3wGryBNU5jCa0BNhhmUWW2Ey9hzuX+Q8/mEz2YbUqXYdOYn8KakEo4VLi6gP0cBzSf3pTrbuC/Yta1POWB29j6tb8/JGIxG48N1BjUO14haa1ajAXW7sI7ffxU8p9rjpUorcy+cbYsMBVXzpeLYLUc33qggA53JguZfuN5K29wIfSIpafkpw1VU1krpkT+GeMJ7Di+nbjVc8FFfEgde0Ec6ExUukzjsJG0j/aBo2pro0B/Abtfx2HuRkhuLSITf5HWV36WcBeaczcNhmHQSh3SPnLjldwPhegqbIA+o03mm4sO73JGKA9S/iI/APYiVxCdNYBOGhnpdVsvo1ihJb5iXiTOndlUL7XBIC85m/ZBzltW12nz0NrdCk96er0R+d4/91mydjbOvWhih19TTgw8zvRQY3Itvn8WWzgdnowpbU/SA81oYVVvHMYS9s7Ig2idxnu0H/xJXcvndLLYopOkv5Yj6PfXxnNEz/H7g6ke6V9FV/9ax/V8w4Rl9)

Note that you should always perform the full computation before comparing and returning the old value, so that the same dependencies can be collected on every run.

## General Optimizations {#general-optimizations}

> The following tips affect both page load and update performance.
Expand Down
2 changes: 1 addition & 1 deletion src/guide/essentials/computed.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ Now when you run `fullName.value = 'John Doe'`, the setter will be invoked and `

### Getters should be side-effect free {#getters-should-be-side-effect-free}

It is important to remember that computed getter functions should only perform pure computation and be free of side effects. For example, **don't make async requests or mutate the DOM inside a computed getter!** Think of a computed property as declaratively describing how to derive a value based on other values - its only responsibility should be computing and returning that value. Later in the guide we will discuss how we can perform side effects in reaction to state changes with [watchers](./watchers).
It is important to remember that computed getter functions should only perform pure computation and be free of side effects. For example, **don't mutate other state, make async requests, or mutate the DOM inside a computed getter!** Think of a computed property as declaratively describing how to derive a value based on other values - its only responsibility should be computing and returning that value. Later in the guide we will discuss how we can perform side effects in reaction to state changes with [watchers](./watchers).

### Avoid mutating computed value {#avoid-mutating-computed-value}

Expand Down

0 comments on commit 5e486e4

Please sign in to comment.