@memoized
Caches a method's answer, keyed by its arguments. Called again with the same arguments, it hands back the same value — the same object, not an equal one.
The situation it is for
A list where every row has a delete button. The row needs a handler that knows which row it is, so the obvious spelling builds one in the markup:
class Row extends Component<{ id: string; onRemove: () => void }> {
render() {
return <li><button onclick={this.props.onRemove}>{this.props.id}</button></li>;
}
}
class Table extends Component<{ rows: string[] }> {
drop(id: string) {}
render() {
return list(this.props.rows, (id) => (
<Row key={id} id={id} onRemove={() => this.drop(id)} />
));
}
}
That arrow is a new function every render, so every row is handed a changed prop and every row
re-renders — a thousand rows redrawn because one of them was deleted. ramonda-check reports it as
function-built-in-the-markup.
@memoized gives each id its own handler, and the same one back next time:
class Row extends Component<{ id: string; onRemove: () => void }> {
render() {
return <li><button onclick={this.props.onRemove}>{this.props.id}</button></li>;
}
}
class Table extends Component<{ rows: string[] }> {
@memoized
remover(id: string) {
return () => this.drop(id);
}
drop(id: string) {}
render() {
return list(this.props.rows, (id) => (
<Row key={id} id={id} onRemove={this.remover(id)} />
));
}
}
Now a row's onRemove changes only when its id does — so deleting one row leaves the others
holding the very handler they already had, and none of them is redrawn.
It caches a value, not only a handler
Nothing about it is specific to functions. Any expensive answer keyed by an argument works the same way — see Caching for which of the two decorators a case wants.
What it refuses
Anything but a method. It keys a cache by arguments, and a field has none.
What it costs
One cache per instance, keyed by the arguments, and it is never evicted while the component lives. A method called with unbounded arguments grows a map that only the component's removal clears.
The arguments have to be keyable. They are compared as a key, so two structurally identical
objects are two entries — and an argument that cannot be a stable key is reported as
unkeyable-memoized-argument.
It is not free where nothing was wrong. A method returning a scalar of its arguments is cheaper to call than to look up. Reach for it where the identity matters — a handler, an object handed to a child — or where the work is real.