-
Notifications
You must be signed in to change notification settings - Fork 75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reactivity #37
Comments
I wonder how you would accomplish that with this library. You would need some analog to react's setState / useState so that updates to a component's state happened in a way the framework would be aware of. That kind of combats the entire idea of "just JavaScript" in the sense that state is no longer just variables in a function. I'm not asserting if that's good / bad or right / wrong. Having a mechanism to say "hey I have these values only state and if they change you should refresh" might be a good idea. Maybe this functionally could or should be some kind of a plugin instead of baked into the core framework. Then developers could opt into that kind of behavior. |
@JasonMatthewsDev have you seen how reactivity systems like Vue and MobX works? Basically:
Here is a tiny implementation from a while ago and here is how vue does it. It's not a new idea. I think it would be a lot better to have as a core part of the framework (take MobX or another reactivity library in) in order to create ergonomic UX. |
@benjamingr thanks. I'm familiar with those patterns. There's just something so pure about a vanilla function with vanilla js variables manipulation that I really like. I suppose even if it were made part of the core framework, the developer could still opt in to using it or not. |
The reason I like those patterns is precisely because it's just vanilla code - you end up writing JavaScript and the reactivity is from the proxies. The code consuming your code doesn't care about the fact you're reactive :] |
Yes, in the sense that you can just get and set properties on your reactive object right? But no in the sense that I can't just declare a new variable in my function and get the same reactivity. So I guess what I'm saying is that it still adds the, albeit small, burden of additional domain knowledge. There's already the burden of having to explicitly refresh a component so I'm not saying this is a reason against doing it, just having a dialog. |
I considered using a proxy-based API, but I typically try to avoid proxies because I dislike metaprogramming, and didn’t see what the clear benefits would be. For instance, I am puzzled by people who say The concrete reasons why I think using local variables and
function *Greeting({name}) {
yield <div>Hello {name}</div>;
for (const {name: newName} of this) {
if (name !== newName) {
yield (
<div>Goodbye {name} and hello {newName}</div>
);
} else {
yield <div>Hello again {newName}</div>;
}
name = newName;
}
}
renderer.render(<Greeting name="Alice" />, document.body);
console.log(document.body.innerHTML); // "<div>Hello Alice</div>"
renderer.render(<Greeting name="Alice" />, document.body);
console.log(document.body.innerHTML); // "<div>Hello again Alice</div>"
renderer.render(<Greeting name="Bob" />, document.body);
console.log(document.body.innerHTML); // "<div>Goodbye Alice and hello Bob</div>"
renderer.render(<Greeting name="Bob" />, document.body);
console.log(document.body.innerHTML); // "<div>Hello again Bob</div>" How do we compare old and new state with proxies? Not clear.
In short, I think that using proxies would have made Crank executionally opaque, less explicit, less powerful, and paradoxically, more prone to infinite loop bugs. However, I’ll try to keep an open mind, and if there is a way to design a proxy-based API which solves the problems above by refreshing the component asynchronously and coalescing assignments, I’m curious to see what you’d come up with. I’ve been trying to think of a way to create a plugin system so people could dynamically and globally extend the However, one thing I would also say is maybe we have different conceptions of what “reactive programming” is? I don’t think reactive = proxies and I think there are a lot of cool reactive programming patterns you can explore, for instance, with async iterators. For instance, because async function *ChatApp() {
yield *switchMap(this, async function *(props) {
const messages = [];
for await (const message of roomMessages(props.room)) {
messages.push(message);
yield (
<ChatLog messages={messages} />
);
}
});
} You can think of ways to use the async iterator of props as a source with various combinators to produce elements. That feels closer to reactive programming to me than anything proxies would bring. |
I like explicit refresh. |
The pattern is typically called a "trampoline". You push all state updates into an array (well, a deque typically) after a microtick (Promise.resolve().then(process)) you process all of them at once. That has an advantage (batching) but also a disadvantage (sync-ness).
I think that breaks the abstraction, relying on old props is not a great pattern. It's entirely possible with proxies though. With MobX or Vue for example you'd just keep a reference to an old value as a regular JS variable reference.
You would just keep a reference to the old value since it's a JS variable?
I agree, though that doesn't necessarily mean no reactivity.
Well, with vue svelte and mobx only the component that is depended on gets refreshed that is:
|
Also, this made me laugh
Coming from someone working on a framework 😅 🙇♂️ |
Hm. Am I correct in observing that, because Crank offers some fairly low level primitives, in effect, building a framework on the framework... or really, a common class/function/object to use... that does this kind of trapping and batching would be easily possible, while still leaving explicit refresh the default? It seems to me that Crank offering a useful lower-level abstraction here can and should be exploited. React has its Redux, Crank can have its Clutch. |
The conflict I see is with pull/push based semantics on a wide scale. Sure the refresh function could be masked automatically but that does not make something reactive. If React classes state object had been a proxy that would have not made it any more reactive. Batching is still completely possible with a proxy but that's not the issue. The elegance of this solution is the complexity isn't in the data. See reactive libraries push complexity into the data, and VDOM libraries into the View. What is so refreshing here is how transparent the progression of data over time is. I'm not going to say I personally need or want that kind of transparency, but I have to admire it. Ok let me put it forward this way. The Reactive system that MobX brings to say React is great way to model things especially coming from store propagation but in modern hooks land is basically an analogue. Now put that in scope here. Your components are basically represented in time-based slices, what sort of reactive tracking and updates can it do that wouldn't be better represented using say async generators. I'm trying to picture what the reactive context would be that you would retrigger and what the lifecycle would be. Would you track at each slice and then when one of its values update, clear dependencies and go to the next and track that. Fine Grained Reactive libraries like Vue and Mobx are all about tracking dependencies and re-running something over and over based on those dependencies changing. Now I imagine not everyone is actually thinking actual Reactive like MobX and Vue and just doesn't want to call an explicit function. To which I'd ask is it really worth it here. Everything the library is doing is pointing to simple data. All this to prevent an explicit function call? There has to be one regardless whether you are hiding it or not. I admit if coming from |
Well, you can use MobX with Crank. I hacked out a working component
Without setTimeout there would be error.
Maybe some kind of wrapper could be created to hook MobX and Crank together, so I think |
What exactly is the motivation of this discussion?
Finding it "frustrating" that Crank's API is different to other frameworks is entirely subjective (and seems mostly at odds with Crank's goals). Are there any concrete problems with Crank's API that we're trying to solve in this issue? E.g. someone mentioned that it's possible to forget the |
I want to use Crank and I don't want to use an API where writing bugs is easy.
In my opinion very likely. This has been likely in AngularJS for example (with forgetting $digests), in backbone (forgetting renders) and in other libraries/frameworks (forgetting INotifyPropertyChanged in WPF). Note that "forgetting" isn't just "I literally forgot", it could be an exception, an unresolved promise with a refresh following etc.
For me, that's a show stopper and I would not use a tool that's error prone in this particular way. I am just one person and Crank can be wildly successful without my usage or endorsement.
It's more about falling into the pit of success. Always having a thing you can forget to do is an API pitfall. That's why I fought so hard for promise APIs where you don't have to check APIs matter, especially at the framework level. |
This might be a silly idea, but going with the concept of falling into the pit of success -- what about inverting the logic so that instead of having to call refresh, it refreshes automatically at a certain point unless you call "don't refresh"? |
I will just add to this discussion. Some mention that refreshes should be automatic or you might forget to refresh. Also stating that other libraries fix this problem. Actually what problem I have with those other libraries is that they are doing too many refreshes where they shouldn't. Sometimes it's really hard in the complex system to write it in the performant way. So I guess both ways are wrong and have the same problem in nature. So I don't think one is worse or better than another. It's just other way of approaching the same problem: how to do updates in a performant way. |
Just a few remarks:
If you compare the example with its React couterpart, you'll see that normally in React you do not have this class of pitfalls that often (of course for example in refs the same is also possible and of course React has its own additional classes of pitfalls): function Greeting({ name }) {
const prevName = usePrevious(name)
if (!prevName) {
return <div>Hello{name}</div>
} else if (prevName === name) {
return <div>Hello again {name}</div>
}
return <div>Goodbye {prevName}, hello {name}</div>
} |
Hey, any reason not to make the framework reactive? Having to
.refresh
after making an uncontrolled change rather than reactive programming like MobX, Svelte or Vue is rather frustrating :]The text was updated successfully, but these errors were encountered: