Giter VIP home page Giter VIP logo

Comments (20)

Pauan avatar Pauan commented on July 30, 2024 2

In addition to the usual vdom libraries (of which there are already many), I think we should also have support for a very different sort of DOM library, based on Signals.

I've used this style of DOM library for many years, and my most recent work is dominator, a full-featured and very fast DOM library.

Unlike vdom, it doesn't do any DOM diffing, so it's extremely fast (usually constant time), stack-allocated (zero-cost), it has great support for integrating with other DOM libraries, and it has excellent support for animations.

The reason I'm mentioning it here is because I think there is some overlap between it and the typical vdom style. So there might be some co-evolution, especially in the interfaces.

from gloo.

aidanhs avatar aidanhs commented on July 30, 2024 2

Just want to give a shout-out to my pet issue in the vdom space - controlled components (yewstack/yew#233 (comment))!

from gloo.

limira avatar limira commented on July 30, 2024 1

Maybe my poor words cause misunderstand here? The code I link to dominator is also in an event handler and not mutate the state during rendering too. What I refer to is "code that mutates the app state appear in the renderer".

from gloo.

aidanhs avatar aidanhs commented on July 30, 2024 1

@David-OConnor I've not rechecked, but the description of how you fixed it seems reasonable.

@fitzgen dodrio looks quite interesting, but I think it's a slightly lower level so doesn't need to think about the controlled component problem. Specifically, because dodrio gives the user ultimate control over the rerender (and afaikt never does it automatically) it's up to that user to render at appropriate times to avoid state drift (the flip side being that the user needs to know to rerender on every input to a volatile component, easily solved with documentation).

That said, the code you've linked is one thing dodrio does very right, which is to correctly force the rerender of those volatile elements.

from gloo.

David-OConnor avatar David-OConnor commented on July 30, 2024

What are the downsides to that style vs VDOM?

VDOMs are easy to conceptualize and write, but rebuild themselves each update, and require diffing (of varying levels of cleverness) to rerender as little as possible. It seems like if we could avoid these limitations, they would have no use.

from gloo.

David-OConnor avatar David-OConnor commented on July 30, 2024

Is the solution I posted/released in resp to your Seed issue adequate?

from gloo.

Pauan avatar Pauan commented on July 30, 2024

What are the downsides to that style vs VDOM?

The primary downside is needing to learn how to use Signals.

Signals are really great, but they do have a learning curve, and they require you to structure your app differently (and think differently).

(This is similar to how Virtual DOM requires you to structure your app differently from a regular DOM app.)

Signals also encourage a more fine-grained approach to state, unlike Virtual DOM which tends to have a single update function (per component).

That has some advantages, but also some disadvantages too (it makes it harder to see exactly where the state is changing).

After using Signals for years, I personally much prefer them over Virtual DOM, and I don't see much reason to use Virtual DOM anymore. But that's just my personal opinion.

from gloo.

fitzgen avatar fitzgen commented on July 30, 2024

@aidanhs

Just want to give a shout-out to my pet issue in the vdom space - controlled components (DenisKolodin/yew#233 (comment))!

I believe this is being handled by

for Dodrio, correct? Or am I misunderstanding you?

from gloo.

Pauan avatar Pauan commented on July 30, 2024

@fitzgen To be clear, a "controlled component" means the value of the <input> is always synchronized between the app and the DOM.

In order to actually accomplish that, it must attach an input event listener to the <input> which then automatically resets the <input>'s value to the app's state.

This ensures that even if the user is typing in the <input>, the <input> will always match the app's state (rather than becoming out of sync).

This is done automatically by React, but I don't see anything in Dodrio which does that.

This is a rather tricky and nuanced topic, but I agree with @aidanhs that it should at least be possible to create controlled components (even if it's not the default).

from gloo.

limira avatar limira commented on July 30, 2024

(it makes it harder to see exactly where the state is changing)

@Pauan: Unfortunately, such disadvantage appear big to me. I hate diving into the render method to find a piece of code that mutates the state. (For example, I don't want to see things like this in my renderer).

Is there any chance to create a framework, base on signals, that forbids users from changing the app state in the render method?

from gloo.

Pauan avatar Pauan commented on July 30, 2024

Unfortunately, such disadvantage appear big to me.

I thought so too at first, but in practice it's not a problem. For better or worse, Rust is not a pure language like Haskell, and Rust can have side effects pretty much anywhere. So dominator is consistent with that.

I tend to structure my dominator apps similar to how I would structure my normal Rust programs, using structs and privacy to carefully control mutation. That means that mutation is almost always local to a particular component, and so it avoids most of the issues with mutation.

I consider that a big advantage of dominator: you can just use normal Rust idioms, and don't need to restructure your app into a vdom-specific way.

I'd also like to note that dominator is quite similar to React: with React you can call setState inside of an event listener inside of the render method, and this is indeed the normal idiomatic way to manage state in React.

This works out okay because setState is local to the component, so the mutation doesn't cause "spooky action at a distance". The same is true with dominator, using structs + privacy to guarantee that mutations are local to the component.

Is there any chance to create a framework, base on signals, that forbids users from changing the app state in the render method?

I don't think that's possible in any Rust framework: mutation is just a fact of life in Rust. Because of interior mutation and I/O, it's not really possible to restrict mutation like that.

On the other hand, is it possible to create a framework which heavily encourages all mutation be done in a single place? Yes, it is.

I've experimented with using queues, which would create a system very similar to Elm, where all mutations are put into a single function. In the end I think the complexity isn't worth it, but other people might think otherwise.

Since I don't want to derail this thread, could you open an issue on the dominator repo, so we can discuss this further?

from gloo.

limira avatar limira commented on July 30, 2024

Thanks for your response! I may go there to discuss this further if I have time to explore more.

from gloo.

chinedufn avatar chinedufn commented on July 30, 2024

I don't think that's possible in any Rust framework: mutation is just a fact of life in Rust. Because of interior mutation and I/O, it's not really possible to restrict mutation like that.

Not to get further off topic but just to share in case it’s useful ... -> I actually think that you can guarantee that state can’t be mutated in views - and do so ergonomically.

On mobile but will try to explain ...

I’ve been using the following approach:

struct Store {
  state: PreventDirectMutation(state)
}

Where Store derefs to State ( PreventDirectMutation also derefs to State).

PreventDirectMutation does not implement DerefMut.

So you pass &Rc<RefCell<Store>> into your views.

They can directly access properties on state immutably and fairly ergonomically because of Deref (although you do need to call .borrow() but in practice that hasn’t felt heavy in any way to me) - but to mutate state they must call my single state updater function. It is impossible to mutate state directly.

The reason it’s &Rc<RefCell<Store>> is to be able to clone it and take ownership in closures.

from gloo.

Pauan avatar Pauan commented on July 30, 2024

@chinedufn If your framework allows for closures with event listeners, then you can always completely bypass the framework's state mechanism and create your own:

fn render() {
    let my_state = Rc::new(RefCell::new(MyState::new(...)));

    html! {
        <div onclick=@{move || {
            let mut lock = my_state.borrow_mut();
            *lock = ...;
        }} />
    }
}

This is because it's always possible to create local variables and move them into closures.

Or you can use lazy_static if you want the state to be shared:

fn render() {
    lazy_static! {
        static ref MY_STATE: Rc<RefCell<MyState>> = Rc::new(RefCell::new(MyState::new(...)));
    }

    let my_state = MY_STATE.clone();

    html! {
        <div onclick=@{move || {
            let mut lock = my_state.borrow_mut();
            *lock = ...;
        }} />
    }
}

So as long as it's possible to use closures, you cannot restrict mutation in Rust.

Maybe if you completely removed all closures you might be able to restrict mutation, but removing closures has a huge ergonomics cost.

from gloo.

Pauan avatar Pauan commented on July 30, 2024

Also, you might dismiss my above argument by saying, "well, that's outside the framework, so it doesn't count".

But because of interior mutability, it's possible to have mutation even with using the framework's state mechanism.

It's quite simple: all I have to do is define State like this:

struct State {
    evil_mutation: RefCell<u32>,
}

And now when my view receives a &Rc<RefCell<Store>> it can Deref that to &State, and then I can use borrow_mut() to mutate the state.

This works because RefCell can be mutated even though it is & and not &mut. This is called "interior mutability".

So even if you only give me & access to the state, I can still mutate it, because of RefCell. It is just fundamentally not possible to restrict mutation in Rust.

So the best way to manage mutations is using Rust's privacy rules, since that guarantees a lack of mutation: if you cannot access something, you cannot mutate it. That's the approach that dominator takes.

from gloo.

Aloso avatar Aloso commented on July 30, 2024

I don't think we need to forbid to mutate the state in a render function. Not doing so is a best practice, but it isn't technically necessary.

We can create mechanisms to compel programmers to write more idiomatic code. If they deliberately evade these mechanisms, that's their problem.

EDIT: I think dodrio handles it pretty well:

impl Render for AppState {
    fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
    where 'a: 'bump {
        // the state can't be mutated, since you have a shared reference...

        use dodrio::builder::*;
        button(bump)
            .child(text("Click me"))
            .on("click", |root, _vdom, _event| {
                let mut state = root.unwrap_mut::<AppState>();
                // ...but you can mutate it in the event listener
            })
            .finish()
    }
}

from gloo.

limira avatar limira commented on July 30, 2024

@Pauan said:

Because of interior mutation and I/O, it's not really possible to restrict mutation like that.

@Aloso:

We can create mechanisms to compel programmers to write more idiomatic code. If they deliberately evade these mechanisms, that's their problem.

Yeah, I agree with that. A framework should try to help users separate the rendering of the view and the updating of the app state.

from gloo.

Pauan avatar Pauan commented on July 30, 2024

@Aloso I think dodrio handles it pretty well

That's not really any different from dominator though, since you can still mutate the state inside of the event listener. So you're still mutating state inside of the render function.

In any case, let's move the discussion about state mutation here: Pauan/rust-dominator#12

from gloo.

Aloso avatar Aloso commented on July 30, 2024

That's not really any different from dominator though, since you can still mutate the state inside of the event listener. So you're still mutating state inside of the render function.

I meant that the event listener isn't called in the render function, so the state is never mutated during rendering. It becomes be more obvious if you replace the closure with a reference to a free-standing function.

from gloo.

yoshuawuyts avatar yoshuawuyts commented on July 30, 2024

Oh, wanted to quickly mention I've been writing about DSLs in Rust, which might be relevant for the discussion here. Currently written 2 of 3 parts.

https://blog.yoshuawuyts.com/dsls-1

I'm working towards an outline of challenges with using proc macros for DSLs + proposal of how we can overcome these challenges. Perhaps it might be interesting for the discussion here.

from gloo.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.