Skip to content

Commit f7334c9

Browse files
authored
Merge pull request #20068 from SolPier/documenting-cached-decorator
[DOC release] Documenting the @cached decorator
2 parents 754b604 + 7f34f1f commit f7334c9

File tree

2 files changed

+94
-0
lines changed

2 files changed

+94
-0
lines changed

packages/@ember/-internals/glimmer/lib/glimmer-tracking-docs.ts

+93
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
88
We can do this by marking the field with the `@tracked` decorator.
99
10+
### Caching a getter value
11+
12+
The `@cached` decorator can be used on getters in order to cache the
13+
return value of the getter.
14+
15+
This method adds an extra overhead to each memoized getter, therefore caching
16+
the values should not be the default strategy, but used in last resort.
17+
1018
@module @glimmer/tracking
1119
@public
1220
*/
@@ -146,3 +154,88 @@
146154
@for @glimmer/tracking
147155
@public
148156
*/
157+
158+
/**
159+
Gives the getter a caching behavior. The return value of the getter
160+
will be cached until any of the properties it is entangled with
161+
are invalidated. This is useful when a getter is expensive and
162+
used very often.
163+
164+
For instance, in this GuestList class, we have the sortedGuests
165+
getter that sorts the guests alphabetically:
166+
167+
```javascript
168+
import { tracked } from '@glimmer/tracking';
169+
170+
class GuestList {
171+
@tracked guests = ['Zoey', 'Tomster'];
172+
173+
get sortedGuests() {
174+
return this.guests.slice().sort()
175+
}
176+
}
177+
```
178+
179+
Every time sortedGuests is accessed, a new array will be created and sorted,
180+
because JavaScript getters do not cache by default. When the guest list
181+
is small, like the one in the example, this is not a problem. However, if
182+
the guest list were to grow very large, it would mean that we would be doing
183+
a large amount of work each time we accessed sortedGetters. With @cached,
184+
we can cache the value instead:
185+
186+
```javascript
187+
import { tracked, cached } from '@glimmer/tracking';
188+
189+
class GuestList {
190+
@tracked guests = ['Zoey', 'Tomster'];
191+
192+
@cached
193+
get sortedGuests() {
194+
return this.guests.slice().sort()
195+
}
196+
}
197+
```
198+
199+
Now the sortedGuests getter will be cached based on autotracking.
200+
It will only rerun and create a new sorted array when the guests tracked
201+
property is updated.
202+
203+
### Tradeoffs
204+
205+
Overuse is discouraged.
206+
207+
In general, you should avoid using `@cached` unless you have confirmed that
208+
the getter you are decorating is computationally expensive. `@cached` adds
209+
a small amount of overhead to the getter, making it more expensive.
210+
While this overhead is small, if `@cached` is overused it can add up to a
211+
large impact overall in your app. Many getters and tracked properties
212+
are only accessed once, rendered, and then never rerendered, so adding
213+
`@cached` when it is unnecessary can negatively impact performance.
214+
215+
Also, `@cached` may rerun even if the values themselves have not changed,
216+
since tracked properties will always invalidate even if their underlying
217+
value did not change.
218+
For example updating an integer value from `5` to an other `5`.
219+
220+
Avoiding a cache invalidation in this case is not something that can
221+
be achieved on the `@cached` decorator itself, but rather when updating
222+
the underlying values, by applying a diff checking mecanism:
223+
224+
```javascript
225+
if (newValue !== this.trackedProp) {
226+
this.trackedProp = newValue;
227+
}
228+
```
229+
230+
Here equal values won't update the property, therefore not triggering a
231+
cache invalidation.
232+
233+
The cost of these edge-guards adds up to the tradoff calculation of using
234+
a caching strategy, hence requiring a very sensitive approach regarding
235+
performance.
236+
237+
@method cached
238+
@static
239+
@for @glimmer/tracking
240+
@public
241+
*/

tests/docs/expected.js

+1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ module.exports = {
9797
'buildRegistry',
9898
'buildRouteInfoMetadata',
9999
'cache',
100+
'cached',
100101
'cacheFor',
101102
'camelize',
102103
'canCatalogEntriesByType',

0 commit comments

Comments
 (0)