Add useful methods to `UseStateHandle` & `UseReducerHandle`
Description
This PR adds:
UseStateHandle::get&UseReducerHandle::getthat return the contained value of the handle in the form in which it's stored internally: in an Rc; These 2 methods will make it easier to work with non(-trivially)-clonable types in said containers;UseStateHandle::into_inner&UseReducerHandle::into_innerthat consume the handle and return its 2 main parts: the inner value and the setter/dispatcher associated with the handle. These methods will allow for avoiding cloning in certain contexts where one would have to useget()+setter()/dispatcher()otherwise.
Checklist
- [x] I have reviewed my own code
- [x] I have added tests
Visit the preview URL for this PR (updated for commit 174d139):
https://yew-rs-api--pr3422-add-handle-methods-dx5xc4eu.web.app
(expires Thu, 05 Oct 2023 20:54:58 GMT)
🔥 via Firebase Hosting GitHub Action 🌎
Benchmark - SSR
Yew Master
| Benchmark | Round | Min (ms) | Max (ms) | Mean (ms) | Standard Deviation |
|---|---|---|---|---|---|
| Baseline | 10 | 360.013 | 360.211 | 360.098 | 0.074 |
| Hello World | 10 | 687.706 | 696.796 | 691.000 | 2.394 |
| Function Router | 10 | 2172.100 | 2202.425 | 2185.140 | 8.032 |
| Concurrent Task | 10 | 1006.855 | 1008.117 | 1007.461 | 0.391 |
| Many Providers | 10 | 1576.487 | 1608.791 | 1590.125 | 11.505 |
Pull Request
| Benchmark | Round | Min (ms) | Max (ms) | Mean (ms) | Standard Deviation |
|---|---|---|---|---|---|
| Baseline | 10 | 359.987 | 360.105 | 360.055 | 0.035 |
| Hello World | 10 | 691.831 | 697.689 | 694.551 | 1.934 |
| Function Router | 10 | 2210.087 | 2235.343 | 2227.472 | 7.332 |
| Concurrent Task | 10 | 1006.772 | 1007.782 | 1007.408 | 0.401 |
| Many Providers | 10 | 1593.305 | 1638.931 | 1606.330 | 13.944 |
Size Comparison
| examples | master (KB) | pull request (KB) | diff (KB) | diff (%) |
|---|---|---|---|---|
| async_clock | 102.886 | 102.886 | 0 | 0.000% |
| boids | 175.667 | 175.667 | 0 | 0.000% |
| communication_child_to_parent | 95.304 | 95.304 | 0 | 0.000% |
| communication_grandchild_with_grandparent | 109.031 | 109.031 | 0 | 0.000% |
| communication_grandparent_to_grandchild | 105.724 | 105.724 | 0 | 0.000% |
| communication_parent_to_child | 92.786 | 92.786 | 0 | 0.000% |
| contexts | 113.452 | 113.452 | 0 | 0.000% |
| counter | 89.195 | 89.195 | 0 | 0.000% |
| counter_functional | 89.932 | 89.932 | 0 | 0.000% |
| dyn_create_destroy_apps | 92.369 | 92.369 | 0 | 0.000% |
| file_upload | 103.515 | 103.515 | 0 | 0.000% |
| function_memory_game | 174.644 | 174.642 | -0.002 | -0.001% |
| function_router | 352.274 | 352.280 | +0.006 | +0.002% |
| function_todomvc | 163.509 | 163.509 | 0 | 0.000% |
| futures | 227.443 | 227.443 | 0 | 0.000% |
| game_of_life | 112.218 | 112.218 | 0 | 0.000% |
| immutable | 188.789 | 188.789 | 0 | 0.000% |
| inner_html | 85.979 | 85.979 | 0 | 0.000% |
| js_callback | 113.463 | 113.467 | +0.004 | +0.003% |
| keyed_list | 201.207 | 201.207 | 0 | 0.000% |
| mount_point | 89.186 | 89.186 | 0 | 0.000% |
| nested_list | 114.614 | 114.614 | 0 | 0.000% |
| node_refs | 96.289 | 96.289 | 0 | 0.000% |
| password_strength | 1721.318 | 1721.318 | 0 | 0.000% |
| portals | 98.365 | 98.365 | 0 | 0.000% |
| router | 318.267 | 318.271 | +0.004 | +0.001% |
| simple_ssr | 144.242 | 144.240 | -0.002 | -0.001% |
| ssr_router | 390.032 | 390.034 | +0.002 | +0.001% |
| suspense | 119.012 | 119.012 | 0 | 0.000% |
| timer | 91.795 | 91.795 | 0 | 0.000% |
| timer_functional | 100.472 | 100.476 | +0.004 | +0.004% |
| todomvc | 143.688 | 143.688 | 0 | 0.000% |
| two_apps | 89.896 | 89.896 | 0 | 0.000% |
| web_worker_fib | 138.877 | 138.875 | -0.002 | -0.001% |
| web_worker_prime | 190.394 | 190.399 | +0.006 | +0.003% |
| webgl | 88.551 | 88.551 | 0 | 0.000% |
✅ None of the examples has changed their size significantly.
The PR does restrict the flexibility in implementation, but on the other hand, how likely is the implementation to change? The current approach works just fine, obtaining/setting the value doesn't require much indirection, and the new methods will allow for easier usage of non(-trivially)-clonable types in the aforementioned handle types.
@futursolo Provide other solutions of being able to encapsulate/not clone setter/dispatcher in Yew then.
-
This PR is best case, since it being tied to implementation helps users to have an easy optimization path and easy access to the current value in a familiar
Clonewrapper (which is being used forReducibleby the way). -
Medium case is introducing a custom wrapper type
UseStateValue<T>instead of giving theRc<T>to the user. This type may contain your wantedscheduler_id, should be able to beCloneasUseStateHandleis, while not forcing clones ofsetter/dispatcherupon the users, as well as allowing the developer to not give power of changing parent's state to children.
#[derive(Clone, ...)] // also ImplicitClone
struct UseStateValue<T> {
inner: Rc<T>, // using same `repr(transparent)`/`transmute` trick
}
impl Deref for UseStateValue<T> {
type Target = T;
...
}
-
Worst case is adding custom wrapper type
UseStateValue<T>that simply wrapsUseStateHandle<T>. This type will not contain.set, but it will still be includingsetter/dispatcherviaUseStateHandle<T>. It will also not be allowed in functions as an argument whereRc<T>parameter is required (functions trying to support this will need to have aimpl Deref<Target = T>parameter instead) (medium case also suffers from this). -
Even worst-er case is not doing anything and making users implement the worst case themselves.
Also possible that use_state will change so much in future that it might not even allow to get a value out of it as easily as Deref (e.g. callback access only). In such case this PR is quite benevolent, since these changes will vanish at that point as well.
The medium case isn't good because oftentimes the value needs to be cloned after getting it out of the state, forcing T to be Clone when it can be avoided. It's either that or recreating the Rc that we just took the value out of.
Yew playground has a component that suffers from this exact same thing today when attaching the change listener to Monaco editor
Concurrent mode, when implemented, will most likely come with breaking changes of its own. I'd rather remove these methods then, providing a suitable alternative, than not add them in the first place