Prototype `HtmlRef` & `use_imperative_handle`
Description
This pull request draft primarily serves a purpose to facilitate discussion mentioned in #2505 and contains a superset of changes in #2551 with an intention to evaluate designs and work towards a minimal changeset that fulfils a typed HtmlRef and ref forwarding.
Rationale
This implementation is a balance between proposals that:
HtmlRef<T>is only available to function components and elements. You need to switch to function components before start usingref. So it does not break anything if you are using struct components. This also makes it possible to avoid any downcasting based behaviour.refis passed as normal props (automatically renamed toref_to avoidr#ref).refis type checked.- An
refmust be set if it presents on ref (orpanic!). refcan be any type for components.
Missing Items
As this is not meant to be a complete pull request so it's still missing a couple items:
- tests & docs
- a complete map of html element types in procedural macro.
Checklist
- [x] I have run
cargo make pr-flow - [x] I have reviewed my own code
- [ ] I have added tests
Visit the preview URL for this PR (updated for commit 0f83a27):
https://yew-rs--pr2567-typed-def-complete-o7z94xmi.web.app
(expires Fri, 08 Apr 2022 16:11:26 GMT)
🔥 via Firebase Hosting GitHub Action 🌎
Size Comparison
| examples | master (KB) | pull request (KB) | diff |
|---|---|---|---|
| boids | 311.369 | 311.563 | +0.194 |
| contexts | 233.381 | 233.786 | +0.405 |
| counter | 164.549 | 164.680 | +0.131 |
| dyn_create_destroy_apps | 172.902 | 174.144 | +1.241 |
| file_upload | 194.978 | 195.123 | +0.146 |
| function_memory_game | 351.644 | 351.679 | +0.035 |
| function_router | 22.254 | 22.254 | 0 |
| function_todomvc | 327.006 | 327.103 | +0.097 |
| futures | 362.344 | 362.643 | +0.299 |
| game_of_life | 207.216 | 207.275 | +0.060 |
| inner_html | 156.415 | 156.616 | +0.201 |
| js_callback | 171.903 | 172.018 | +0.114 |
| keyed_list | 329.064 | 330.574 | +1.510 |
| mount_point | 163.721 | 163.872 | +0.151 |
| nested_list | 225.379 | 226.060 | +0.681 |
| node_refs | 170.802 | 175.362 | +4.561 |
| password_strength | 1851.783 | 1851.886 | +0.103 |
| portals | 184.307 | 186.442 | +2.136 |
| router | 589.899 | 588.960 | -0.939 |
| suspense | 222.801 | 223.582 | +0.781 |
| timer | 170.392 | 170.535 | +0.144 |
| todomvc | 270.743 | 272.073 | +1.330 |
| two_apps | 166.012 | 166.130 | +0.118 |
| webgl | 170.817 | 172.210 | +1.393 |
@WorldSEnder
Although I have no intention to make this pull request mergable, I hope this answers your question about the ground why I am objective to separation on refs of elements and components (and any special treatment introduced as a side effect of the separation) and why we really shouldn't try to over engineer this concept.
I definitely appreciate the effort. It could take a while to come up with a better version of #2551 since I have to do some stuff IRL, but I'll try to incorporate it.
Sorry, I mis clicked. Didn't mean to mark it as ready for review
As I understand, we don't expose a way to call methods on a component. What if the function component wants to allow calling method to perform an action? The nested list example exposes WeakComponentLink for this purpose. As I understand it, the API this PR provides does not allow us to provide that implementation
@hamza1311 it does, for function components, with use_imperative_ref. The way you would use it, if I understand it correctly, is along the lines of
#[derive(Clone)]
struct Controller; // the bound structure, could contain a Scope<Comp>
// i.e. a link to the associated component to send messages/set_state etc
impl Controller {
fn some_function(&self) { .. }
}
struct Props {
handle: HtmlRef<Controller>,
}
#[function_component]
fn Component(props: &Props) -> Html {
use_imperative_handle(props.handle, || Controller);
html! {
..
}
}
#[function_component]
fn Consumer() -> Html {
let handle = use_html_ref::<Controller>();
let _onclick = {
let handle = handle.clone();
// Unwrap okay while the Component is mounted
Callback::from(|_| handle.get().unwrap().some_function())
};
html! { <Component {handle} /> }
}
@hamza1311 The usage is like how @WorldSEnder mentioned.
Any prop will work as an imperative handle or forwarded ref, the only difference between ref and other prop is that it will check if you actually have set the handle for each render and it is limited to be an HtmlRef<T>. You should be capturing hook handles in the imperative handle constructor so that methods on the handle type can call them to mutate / read states of the component.
As now I have implemented this prototype, the advantage of a unified design has become clearer. To summarise:
- Small changeset (+149 lines).
- Minimal impact on code size if no
refis used (<0.3KB). - Flexible. As an
refcan be any type, it can be forwarded to any element and component as-is (given type matches). And can register additional methods when combined with dereferencing anduse_imperative_handle. use_imperative_handlecan be implemented as a simple effect.- Supports multiple refs. You can have an
refandinput_refforwarded to the root container element and the input element respectively. - Bidirectional. It also supports reading information from the component compared to a message-based system.
I see. use_imperative_handle seems "magical" though. But I guess that's the spirit of function components
I was thinking of taking a shot at this. What's needed to make this PR ready to be merged?
I was thinking of taking a shot at this. What's needed to make this PR ready to be merged?
- Resolve conflicts
- Add tests
- Remove the requirement of a ref must be set by rendered. (This is due to if the ref is forwarded and the child component becomes suspended, it cannot be guaranteed to have an ref set by rendered.)