Skip to content

Commit 622ee60

Browse files
committed
match guides docs
1 parent c2b9055 commit 622ee60

File tree

1 file changed

+90
-29
lines changed
  • packages/@ember/-internals/metal/lib

1 file changed

+90
-29
lines changed

packages/@ember/-internals/metal/lib/cached.ts

+90-29
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,96 @@ import { createCache, getValue } from '@glimmer/validator';
77
/**
88
* @decorator
99
*
10-
* Memoizes the result of a getter based on autotracking.
11-
*
12-
* The `@cached` decorator can be used on native getters to memoize their return
13-
* values based on the tracked state they consume while being calculated.
14-
*
15-
* By default a getter is always re-computed every time it is accessed. On
16-
* average this is faster than caching every getter result by default.
17-
*
18-
* However, there are absolutely cases where getters are expensive, and their
19-
* values are used repeatedly, so memoization would be very helpful.
20-
* Strategic, opt-in memoization is a useful tool that helps developers
21-
* optimize their apps when relevant, without adding extra overhead unless
22-
* necessary.
23-
*
24-
* @example
25-
*
26-
* ```ts
27-
* import { tracked, cached } from '@glimmer/tracking';
28-
*
29-
* class Person {
30-
* @tracked firstName = 'Jen';
31-
* @tracked lastName = 'Weber';
32-
*
33-
* @cached
34-
* get fullName() {
35-
* return `${this.firstName} ${this.lastName}`;
36-
* }
37-
* }
38-
* ```
10+
Gives the getter a caching behavior. The return value of the getter
11+
will be cached until any of the properties it is entangled with
12+
are invalidated. This is useful when a getter is expensive and
13+
used very often.
14+
15+
For instance, in this `GuestList` class, we have the `sortedGuests`
16+
getter that sorts the guests alphabetically:
17+
18+
```javascript
19+
import { tracked } from '@glimmer/tracking';
20+
21+
class GuestList {
22+
@tracked guests = ['Zoey', 'Tomster'];
23+
24+
get sortedGuests() {
25+
return this.guests.slice().sort()
26+
}
27+
}
28+
```
29+
30+
Every time `sortedGuests` is accessed, a new array will be created and sorted,
31+
because JavaScript getters do not cache by default. When the guest list
32+
is small, like the one in the example, this is not a problem. However, if
33+
the guest list were to grow very large, it would mean that we would be doing
34+
a large amount of work each time we accessed `sortedGuests`. With `@cached`,
35+
we can cache the value instead:
36+
37+
```javascript
38+
import { tracked, cached } from '@glimmer/tracking';
39+
40+
class GuestList {
41+
@tracked guests = ['Zoey', 'Tomster'];
42+
43+
@cached
44+
get sortedGuests() {
45+
return this.guests.slice().sort()
46+
}
47+
}
48+
```
49+
50+
Now the `sortedGuests` getter will be cached based on autotracking.
51+
It will only rerun and create a new sorted array when the guests tracked
52+
property is updated.
53+
54+
55+
### Tradeoffs
56+
57+
Overuse is discouraged.
58+
59+
In general, you should avoid using `@cached` unless you have confirmed that
60+
the getter you are decorating is computationally expensive, since `@cached`
61+
adds a small amount of overhead to the getter.
62+
While the individual costs are small, a systematic use of the `@cached`
63+
decorator can add up to a large impact overall in your app.
64+
Many getters and tracked properties are only accessed once during rendering,
65+
and then never rerendered, so adding `@cached` when unnecessary can
66+
negatively impact performance.
67+
68+
Also, `@cached` may rerun even if the values themselves have not changed,
69+
since tracked properties will always invalidate.
70+
For example updating an integer value from `5` to an other `5` will trigger
71+
a rerun of the cached properties building from this integer.
72+
73+
Avoiding a cache invalidation in this case is not something that can
74+
be achieved on the `@cached` decorator itself, but rather when updating
75+
the underlying tracked values, by applying some diff checking mechanisms:
76+
77+
```javascript
78+
if (nextValue !== this.trackedProp) {
79+
this.trackedProp = nextValue;
80+
}
81+
```
82+
83+
Here equal values won't update the property, therefore not triggering
84+
the subsequent cache invalidations of the `@cached` properties who were
85+
using this `trackedProp`.
86+
87+
As a reminder, do not use this piece of code inside a tracked getter,
88+
as the dependency chain could lead to an infinite loop. Mutating an adjacent
89+
property from a getter is not a good practice anyway, even with a caching
90+
mechanism reducing reruns.
91+
92+
The cost of these edge-guards adds up to the trade-off calculation of using
93+
a caching strategy, hence requiring a very sensitive and moderate approach
94+
regarding performance.
95+
96+
@method cached
97+
@static
98+
@for @glimmer/tracking
99+
@public
39100
*/
40101
export const cached: PropertyDecorator = (...args: any[]) => {
41102
const [target, key, descriptor] = args;

0 commit comments

Comments
 (0)