feat: Add ui.component memoization and selective re-rendering#1296
feat: Add ui.component memoization and selective re-rendering#1296mofojed wants to merge 17 commits intodeephaven:mainfrom
Conversation
f383c9d to
e85f1bc
Compare
| ) | ||
| ``` | ||
|
|
||
| --- |
There was a problem hiding this comment.
With this notation we could also write:
memo_parent = ui.memo(parent)
5e1318c to
305fc47
Compare
jnumainville
left a comment
There was a problem hiding this comment.
Just gave some comments on the two options.
| raise TypeError( | ||
| f"@ui.memo can only be used with @ui.component decorated functions. " | ||
| f"Got {type(element).__name__} instead." | ||
| ) |
There was a problem hiding this comment.
I think the fact that we are throwing this error after checking for @ui.component is a point against this option.
A third option would be that @ui.memo creates a ui.component under the hood since it has to be one anyways, but then it would have to duplicate arguments if we add more to ui.component, so more to maintain. I don't love that option either.
There was a problem hiding this comment.
Yea after playing with @ui.memo more, I think it makes the most sense to just do the @ui.component(memo= option.
| - ❌ Two decorators required (more verbose) | ||
| - ❌ Easy to get decorator order wrong (`@ui.component` then `@ui.memo` won't work) |
There was a problem hiding this comment.
Not sure if there is more possible decorators (routers?), but these cons would compound quickly if we did have any others.
There was a problem hiding this comment.
If we thought of the decorators as just wrapper components like React, then the order at least makes intuitive sense
But I'm not sure I have a strong opinion on either syntax
|
|
||
| **Cons:** | ||
|
|
||
| - ❌ Cannot memoize third-party components |
There was a problem hiding this comment.
I'm not sure I understand this con, or at least I don't think it's meaningful? It would be easy enough to take third-party components and put them in your own memoized component without any real problems?
Maybe it's saying you can't do something like ui.memo(external_ui_component, ...) directly, but you can just wrap it in another component, and that isn't substantially more difficult.
There was a problem hiding this comment.
Agreed, the con is minimal
|
|
||
| ## Recommendation | ||
|
|
||
| **Implement both options**, with Option B (`memo=`) as the primary API and Option A (`@ui.memo`) for advanced use cases. |
There was a problem hiding this comment.
I'd say only B is my choice. Easier to maintain, much simpler to use, and I don't think these advanced use cases are really meaningful.
Two options for props-based memoization to skip re-renders: - Option A: @ui.memo decorator (familiar to React devs) - Option B: @ui.component(memo=True|compare_fn) parameter (cleaner) Includes: - API design and implementation details - MemoizedFunctionElement and Renderer changes - Unit tests for both options - Performance benchmarks - Comparison and recommendation (implement both)
- Checks props and if they're the same, just return the previously rendered node
- Still need to clean up the `_default_are_props_equal` and how children are handled, I think?
- Also need to add a bunch of unit tests. But it more or less works!
```
from deephaven import ui
def are_props_equal(old_props, new_props):
print(f"Checking props {old_props} vs {new_props}")
return old_props == new_props
@ui.component
def foo_component(name):
value, set_value = ui.use_state(0)
print(f"foo {name} render")
return ui.button(f"foo {name} {value}", on_press=lambda: set_value(value+1))
@ui.memo(are_props_equal=are_props_equal)
@ui.component
def memo_foo_component(name):
value, set_value = ui.use_state(0)
print(f"memo_foo {name} render")
return ui.button(f"foo {name} {value}", on_press=lambda: set_value(value+1))
memo_foo = ui.memo()(foo_component)
@ui.component
def bar_component():
value, set_value = ui.use_state(0)
return ui.flex(
foo_component("A"),
foo_component("B"),
memo_foo_component("X"),
memo_foo("Y"),
ui.button(f"bar {value}", on_press=lambda: set_value(value+1))
)
mf = memo_foo_component("mf")
b = bar_component()
```
- Allow @ui.memo syntax in addition to @ui.memo() - Add tests for custom are_props_equal functions: - Deep equality comparison for object props - Always rerender (returns False) - Always skip (returns True) - Selective prop comparison - Threshold-based comparison
- Update render-cycle.md with section on optimizing re-renders - Create memoizing-components.md with comprehensive guide: - Basic usage and how memoization works - When to use @ui.memo - Custom comparison with are_props_equal - Common pitfalls (new objects, callbacks) - Comparison with use_memo hook - Add memoizing-components to sidebar navigation
…nent - Modified component.py to add memo parameter (True/False/callable) - Removed standalone memo.py decorator - Updated components/__init__.py exports - Updated all tests to use @ui.component(memo=True) syntax - Updated documentation in memoizing-components.md and render-cycle.md
- No special treatment for children
- Now it is optimized to only re-render when necessary - Needed to fix up some existing tests that was relying on the previous non-optimized behaviour - Added some unit tests
- BREAKING CHANGE: No longer allowing GeneratorType to be returned as children - Doesn't really make sense anyways, as we consume it all immediately so no real savings over a `list`... - Should update typing to match
- Don't allow `GeneratorType` as a type for children - Now `children` is a more specific type
c56be04 to
0241cc8
Compare
- Otherwise we get errors when reloading widgets
- Changes PropsType to a Mapping instead of a Dict. Allows TypedDicts to be passed in as well then, and we don't need to modify props so it's more accurate - Add `float` to NodeType - Added a note to update a few spots where we pass a Table or ItemTableSource back directly, rather than wrapping them in an Element or something else
|
ui docs preview (Available for 14 days) |
memoparameter to@ui.componentto memoize a component, or pass a custom memoization function for checking behaviour