yew icon indicating copy to clipboard operation
yew copied to clipboard

Prototype `HtmlRef` & `use_imperative_handle`

Open futursolo opened this issue 3 years ago • 11 comments

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:

  1. HtmlRef<T> is only available to function components and elements. You need to switch to function components before start using ref. So it does not break anything if you are using struct components. This also makes it possible to avoid any downcasting based behaviour.
  2. ref is passed as normal props (automatically renamed to ref_ to avoid r#ref).
  3. ref is type checked.
  4. An ref must be set if it presents on ref (or panic!).
  5. ref can 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:

  1. tests & docs
  2. 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

futursolo avatar Apr 01 '22 15:04 futursolo

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 🌎

github-actions[bot] avatar Apr 01 '22 15:04 github-actions[bot]

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

github-actions[bot] avatar Apr 01 '22 15:04 github-actions[bot]

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

futursolo avatar Apr 01 '22 17:04 futursolo

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.

WorldSEnder avatar Apr 01 '22 18:04 WorldSEnder

Sorry, I mis clicked. Didn't mean to mark it as ready for review

ranile avatar Apr 01 '22 18:04 ranile

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

ranile avatar Apr 01 '22 18:04 ranile

@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} /> }
}

WorldSEnder avatar Apr 01 '22 19:04 WorldSEnder

@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:

  1. Small changeset (+149 lines).
  2. Minimal impact on code size if no ref is used (<0.3KB).
  3. Flexible. As an ref can 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 and use_imperative_handle.
  4. use_imperative_handle can be implemented as a simple effect.
  5. Supports multiple refs. You can have an ref and input_ref forwarded to the root container element and the input element respectively.
  6. Bidirectional. It also supports reading information from the component compared to a message-based system.

futursolo avatar Apr 02 '22 03:04 futursolo

I see. use_imperative_handle seems "magical" though. But I guess that's the spirit of function components

ranile avatar Apr 02 '22 15:04 ranile

I was thinking of taking a shot at this. What's needed to make this PR ready to be merged?

ranile avatar Jun 30 '22 20:06 ranile

I was thinking of taking a shot at this. What's needed to make this PR ready to be merged?

  1. Resolve conflicts
  2. Add tests
  3. 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.)

futursolo avatar Jun 30 '22 21:06 futursolo