Skip to content

Commit f64f5e1

Browse files
authored
Merge pull request #20543 from NullVoxPopuli/add-jsdoc-for-cached
2 parents f8001db + e0bb76a commit f64f5e1

File tree

1 file changed

+89
-1
lines changed
  • packages/@ember/-internals/metal/lib

1 file changed

+89
-1
lines changed

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

+89-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,95 @@
44
import { DEBUG } from '@glimmer/env';
55
import { createCache, getValue } from '@glimmer/validator';
66

7-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
7+
/**
8+
* @decorator
9+
*
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+
Remember that setting tracked data should only be done during initialization,
88+
or as the result of a user action. Setting tracked data during render
89+
(such as in a getter), is not supported.
90+
91+
@method cached
92+
@static
93+
@for @glimmer/tracking
94+
@public
95+
*/
896
export const cached: PropertyDecorator = (...args: any[]) => {
997
const [target, key, descriptor] = args;
1098

0 commit comments

Comments
 (0)