Ramonda

Performance

Most of what a performance page usually teaches does not apply here, and the reason is the reactivity model: a component re-renders when its own state changes, and nothing else does. There is no tree-wide diff to opt out of, so there is nothing to wrap components in by default.

Which leaves a much smaller subject: one class of mistake that costs renders, three declarations that answer it, and two ways to see what your app is actually doing.

What is already free

A change re-renders one component, and patches only the DOM that differs. Appending a row to a table of ten rebuilds the page component and not the ten rows — list() keeps each row's scope and its node, so the rows are never asked to render.

A context consumer wakes per key. Reading theme from a context whose tick moved does not re-render you.

With one caveat, and it is the commonest surprise here: a key holding an object literal is a new object every time the provider's callback runs, and a new object is a changed key — so every consumer of that key wakes however unchanged the contents are. That is what stableProps below is for. See an object as a value.

A hook's props callback is fine-grained, unlike a component's own state: it re-runs when something it read changed, which is what lets a hook take a live value without its owner re-rendering. See where fine-grained tracking does live.

None of that needs asking for. If a commit rebuilds more than you expected, the cause is almost always the next section.

The one mistake that costs renders

Every prop is a signal, and a signal compares by reference. So a value built during the render is a changed prop every time — and a changed prop recomputes every @compute reading it, fires every @watchProp on it, and reconnects a subscription whose connect read it.

<Row item={item} tags={[...item.tags]} />   {/* a new array every render */}

Nothing about that line looks expensive. The cost is downstream, and it is why the framework reports it: RMD020 for a value render() produced differently the second time, RMD022 for a hook's props callback doing the same, and RMD024 for a @compute that recomputes without its answer changing.

ramonda-check finds the same class before anything runs, and the ones that come up most are fresh-object-in-props, function-built-in-the-markup, arrow-fields, ref-built-where-it-cannot-be-kept and index-as-key.

The three declarations

Each says something different about a value, and picking the wrong one is why they are worth distinguishing.

it saysreach for it when
@computethis value is derived, and is recomputed only when what it reads changesthe value is a function of state you already hold
@memoizedcache this by its arguments, per instancethe value has to be built per item — a handler for a row
@StablePropsthis prop is a VALUE, so compare its contents rather than its identitythe parent legitimately rebuilds it and you cannot change the parent

A key is the fourth answer and the commonest one: a list whose rows are rebuilt objects keeps every row's state and DOM only if the rows can be told apart. See rendering lists.

A context can make the same declaration at creationcreateContext(value, { stableProps: [...] }) — which is the right end when the keys are the context author's knowledge rather than a consumer's.

Seeing which it is

The devtools PROFILE tab, and the count matters more than the milliseconds. A commit that says Row ×40 after one row changed is not a slow component; it is forty renders that did not need to happen. Recording costs about 3.6% of a commit and nothing at all while stopped. See what a commit cost.

ramonda-check --split answers the other question — what the browser downloads before it does anything. It splits where a bundler splits, at a lazy prop and nowhere else, and reports the first payload, each split point, and what several of them share. See what loads when.

What not to reach for

Do not declare these pre-emptively. @compute on a value nothing reads twice, @memoized on something built once per component, @StableProps on a prop the parent already keeps stable — each adds a comparison and buys nothing, and the checker reports the clearest case as decorator-that-adds-nothing.

A slow render is a different problem from too many renders, and the profile tab tells them apart: one commit taking a long time is work to move — off the render path, into a @compute, or behind a lazy boundary. Forty commits taking no time each is the section above.

And the checks themselves cost nothing in production. The double render behind RMD020, every diagnostic, and the profiler are development-only and are not compiled into a production build.

Next