This guide covers techniques for optimizing vui.el applications.
By default, when state changes, the component and all its children re-render. This is usually fine, but can become slow with:
- Large lists
- Complex computations in render
- Deeply nested component trees
Use :should-update to skip unnecessary re-renders:
(vui-defcomponent expensive-component (data)
:state ((internal 0))
:should-update
;; Only re-render if data prop changed
(not (equal (plist-get props :data)
(plist-get prev-props :data)))
:render
(expensive-render data))In :should-update, you have access to:
props- New propsstate- New stateprev-props- Previous propsprev-state- Previous state
Return t to render, nil to skip.
(vui-defcomponent memoized-display (value)
:should-update
(not (equal (plist-get props :value)
(plist-get prev-props :value)))
:render
(vui-text (format "Value: %s" value)))(vui-defcomponent selective-updates (data)
:state ((ui-state 'normal))
:should-update
;; Re-render for data changes, not UI state changes
(or (not (equal (plist-get props :data)
(plist-get prev-props :data)))
(eq (plist-get prev-state :ui-state) 'loading))
:render ...)Cache expensive computations:
(vui-defcomponent filtered-list ()
:state ((items large-list)
(filter ""))
:render
(let ((filtered (use-memo (items filter)
;; Only recomputes when items or filter changes
(seq-filter
(lambda (item)
(string-match-p filter (plist-get item :name)))
items))))
(vui-list filtered #'render-item)))- Filtering/sorting large lists
- Complex transformations
- Derived data from props/state
For performance, use eq comparison when appropriate:
;; Default: structural comparison (equal)
(use-memo (data) ...)
;; Identity comparison (faster for symbols, identical objects)
(use-memo* (symbol-key)
:compare 'eq
...)
;; Custom comparison
(use-memo* (data)
:compare (lambda (old new)
(= (length (car old)) (length (car new))))
...)Prevent unnecessary child re-renders by keeping callback references stable:
(vui-defcomponent parent ()
:state ((count 0))
:render
(let ((increment (use-callback (count)
(lambda ()
(vui-set-state :count (1+ count))))))
;; Same function reference if count unchanged
(vui-component 'child :on-action increment)))Multiple state updates can trigger multiple re-renders. Use vui-batch to combine them:
;; Without batching: 3 re-renders
(vui-set-state :a 1)
(vui-set-state :b 2)
(vui-set-state :c 3)
;; With batching: 1 re-render
(vui-batch
(vui-set-state :a 1)
(vui-set-state :b 2)
(vui-set-state :c 3))Event handlers are automatically batched:
(vui-button "Update all"
:on-click (lambda ()
;; These are batched automatically
(vui-set-state :x 1)
(vui-set-state :y 2)))By default, renders are deferred briefly to allow batching. Configure with vui-render-delay:
;; Immediate rendering (might feel more responsive)
(setq vui-render-delay nil)
;; Default: 0.01 seconds delay
(setq vui-render-delay 0.01)
;; Longer delay (for expensive renders)
(setq vui-render-delay 0.1)Use vui-flush-sync when you need immediate updates:
(vui-batch
(vui-set-state :status "Processing..."))
(vui-flush-sync) ; Render now
;; Do expensive work...
(vui-batch
(vui-set-state :status "Done!"))Always provide keys for dynamic lists:
;; Good: stable keys
(vui-list items
(lambda (item)
(vui-component 'item-view
:key (plist-get item :id) ; Stable ID
:item item))
(lambda (item) (plist-get item :id)))
;; Bad: no keys (slower reconciliation)
(vui-list items
(lambda (item)
(vui-component 'item-view :item item)))Keys help vui.el:
- Identify which items changed
- Preserve component state when reordering
- Minimize DOM operations
Enable timing to find slow components:
;; Enable
(setq vui-timing-enabled t)
;; Do some interactions...
;; View report
(vui-report-timing)The report shows time spent in each phase per component.
(vui-get-timing)
;; => ((:phase render :component my-component :duration 0.012 :timestamp ...)
;; (:phase commit :component my-component :duration 0.003 :timestamp ...)
;; ...)- [ ] Use
:should-updatefor components with expensive renders - [ ] Use
use-memofor expensive computations - [ ] Use
use-callbackfor callbacks passed to children - [ ] Use
vui-batchfor multiple state updates - [ ] Provide
:keyfor list items - [ ] Profile with
vui-timing-enabledto find bottlenecks - [ ] Consider
vui-render-delaytuning
Exercise: Optimize a slow list component:
- Create a list of 1000 items
- Add a filter input
- Use
use-memofor filtering- Add
:should-updateto list items- Profile before and after with
vui-report-timing
- Developer Tools - Debugging and inspection
- Hooks - use-memo and use-callback details