Formily Page-Level Re-renders and Long Tasks
Scenario: the observer is too coarse. The page component reads form.values.items to compute a total, so any field change re-renders the whole page and causes a long task.
How to read it: click the blue button (row 0 quantity +1). Rows that flash yellow were re-rendered; the boxes on top show re-rendered rows, main-thread blocking time, and long tasks.
- ❌ Problem: page-wide
observer, 100 rows re-render per input - ✅ Route i:
form.addEffects+onFieldValueChange('items.*.qty'), only the total updates - ✅ Route ii: move
observerdown to the total; eachFieldsubscribes to itself only
When to use which
Why route i exists: observer tracks every reactive value read during render. Once the page reads form.values.items, it subscribes to every field. A manual effect moves that subscription out of render and states it explicitly: which paths to watch, and what to update.
If you just want fewer renders: prefer route ii. Push observer down to the small component that actually uses the data. Least code, most idiomatic Formily.
Use route i (effect) when:
- A legacy page is wrapped in one big
observeryou can’t split yet; move the derived data out first - Changes are frequent and need throttling, debouncing, or batching
- A change triggers side effects: a request, linked fields, or writing the total back to the form (
form.setValuesIn('total', ...)), none of which belong in render - You want derived logic decoupled from UI, maintained in the form model layer
Note: register the effect with a unique id and call form.removeEffects(id) on unmount, or you’ll stack duplicate listeners.