Skip to content

Latest commit

 

History

History
261 lines (192 loc) · 6.08 KB

File metadata and controls

261 lines (192 loc) · 6.08 KB

Performance

This guide covers techniques for optimizing vui.el applications.

1 Understanding Re-renders

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

2 Skipping Renders - :should-update

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))

2.1 Arguments Available

In :should-update, you have access to:

  • props - New props
  • state - New state
  • prev-props - Previous props
  • prev-state - Previous state

Return t to render, nil to skip.

2.2 Example: Skip if Props Unchanged

(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)))

2.3 Example: Skip on Internal State

(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 ...)

3 Memoization - use-memo

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)))

3.1 When to Use

  • Filtering/sorting large lists
  • Complex transformations
  • Derived data from props/state

3.2 Comparison Modes

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))))
  ...)

4 Stable Callbacks - use-callback

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)))

5 Batching Updates

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))

5.1 Automatic Batching

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)))

6 Deferred Rendering

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)

6.1 Force Immediate Render

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!"))

7 Keys for List Reconciliation

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

8 Profiling with Timing

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.

8.1 Timing Data

(vui-get-timing)
;; => ((:phase render :component my-component :duration 0.012 :timestamp ...)
;;     (:phase commit :component my-component :duration 0.003 :timestamp ...)
;;     ...)

9 Performance Checklist

  1. [ ] Use :should-update for components with expensive renders
  2. [ ] Use use-memo for expensive computations
  3. [ ] Use use-callback for callbacks passed to children
  4. [ ] Use vui-batch for multiple state updates
  5. [ ] Provide :key for list items
  6. [ ] Profile with vui-timing-enabled to find bottlenecks
  7. [ ] Consider vui-render-delay tuning

10 Try It Yourself

Exercise: Optimize a slow list component:

  1. Create a list of 1000 items
  2. Add a filter input
  3. Use use-memo for filtering
  4. Add :should-update to list items
  5. Profile before and after with vui-report-timing

11 What’s Next?