Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JSDoc for @cached for the built-in types #20543

Merged
merged 4 commits into from
Sep 8, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 89 additions & 1 deletion packages/@ember/-internals/metal/lib/cached.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,95 @@
import { DEBUG } from '@glimmer/env';
import { createCache, getValue } from '@glimmer/validator';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
/**
* @decorator
*
Gives the getter a caching behavior. The return value of the getter
will be cached until any of the properties it is entangled with
are invalidated. This is useful when a getter is expensive and
used very often.

For instance, in this `GuestList` class, we have the `sortedGuests`
getter that sorts the guests alphabetically:

```javascript
import { tracked } from '@glimmer/tracking';

class GuestList {
@tracked guests = ['Zoey', 'Tomster'];

get sortedGuests() {
return this.guests.slice().sort()
}
}
```

Every time `sortedGuests` is accessed, a new array will be created and sorted,
because JavaScript getters do not cache by default. When the guest list
is small, like the one in the example, this is not a problem. However, if
the guest list were to grow very large, it would mean that we would be doing
a large amount of work each time we accessed `sortedGuests`. With `@cached`,
we can cache the value instead:

```javascript
import { tracked, cached } from '@glimmer/tracking';

class GuestList {
@tracked guests = ['Zoey', 'Tomster'];

@cached
get sortedGuests() {
return this.guests.slice().sort()
}
}
```

Now the `sortedGuests` getter will be cached based on autotracking.
It will only rerun and create a new sorted array when the guests tracked
property is updated.


### Tradeoffs

Overuse is discouraged.

In general, you should avoid using `@cached` unless you have confirmed that
the getter you are decorating is computationally expensive, since `@cached`
adds a small amount of overhead to the getter.
While the individual costs are small, a systematic use of the `@cached`
decorator can add up to a large impact overall in your app.
Many getters and tracked properties are only accessed once during rendering,
and then never rerendered, so adding `@cached` when unnecessary can
negatively impact performance.

Also, `@cached` may rerun even if the values themselves have not changed,
since tracked properties will always invalidate.
For example updating an integer value from `5` to an other `5` will trigger
a rerun of the cached properties building from this integer.

Avoiding a cache invalidation in this case is not something that can
be achieved on the `@cached` decorator itself, but rather when updating
the underlying tracked values, by applying some diff checking mechanisms:

```javascript
if (nextValue !== this.trackedProp) {
this.trackedProp = nextValue;
}
```

Here equal values won't update the property, therefore not triggering
the subsequent cache invalidations of the `@cached` properties who were
using this `trackedProp`.

Remember that setting tracked data should only be done during initialization,
or as the result of a user action. Setting tracked data during render
(such as in a getter), is not supported.

@method cached
@static
@for @glimmer/tracking
@public
*/
export const cached: PropertyDecorator = (...args: any[]) => {
const [target, key, descriptor] = args;

Expand Down
Loading