@@ -7,35 +7,96 @@ import { createCache, getValue } from '@glimmer/validator';
7
7
/**
8
8
* @decorator
9
9
*
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
39
100
*/
40
101
export const cached : PropertyDecorator = ( ...args : any [ ] ) => {
41
102
const [ target , key , descriptor ] = args ;
0 commit comments