3 ms·
While equivalent to a proper approach, it feels like hooks are an unnecessary hack. What could be done is to have the component function return a closure that
by devit 6y ago
While equivalent to a proper approach, it feels like hooks are an unnecessary hack.
What could be done is to have the component function return a closure that returns the VDOM nodes instead of the VDOM nodes themselves, and then the hooks would be run only once, since rerendering would only run the returned closure and not the whole component function, and there would be no need for the magic or rules.
Like this:
const Component = component((comp) => {
const [show, setShow] = useState(comp, false);
return (...props) => (
<div>
<button onClick={() => setShow(!show())}>Show Menu</button>
// Mounted with show = true and unomunted with show = false
{show() && <MenuDropdown />}
</div>
);
});
Where the changes are that the hook takes the component as an argument, show is now a function rather than a value and a closure (that takes the props) is returned rather than returning the VDOM nodes directly.
The React Hooks API is essentially equivalent to this, but inelegant, unintuitive and somewhat inefficient.
- jcelerier 6y ago> What could be done is to have the component function return a closure that returns the VDOM nodes instead of the VDOM nodes themselves, and then the hooks would be run only once, since rerendering would only run the returned closure and not the whole component function, and there would be no need for the magic or rules. what could be done is actually using a proper reactive language where things look like this, instead of hacking HTML, JS and whatnot in an unholy mess of syntax: Button { text: "Show menu" onClicked: menu.visible = !menu.visible } Menu { id: menu } e.g. a bit like this: https://tinyurl.com/yaawdomh https://tinyurl.com/yaawdomh
- earthboundkid 6y agoYou may be interested in https://crank.js.org/blog/introducing-crank https://crank.js.org/blog/introducing-crank which essentially works as you propose.
- spankalee 6y agoWhat's the benefit of this over class syntax? If we had decorators in the language already we could make field reactive: class Component extends React.Component { @state show; render() { return <div> <button onClick={() => this.show = !this.show}>Show Menu</button> // Mounted with show = true and unomunted with show = false {this.show && <MenuDropdown />} </div> } } Hiding the underlying stateful objects that back React components seems like it causes more confusion and complicated APIs than it's worth. Likewise closures returning closures and state defined with function calls seems like re-inventing classes. Just make the component itself stateful and the lifecycle explicit and it's all so much simpler. Then attack the problem of composing JS classes from pieces with tools available to all JS classes, not just the framework at hand.
- obedm 6y ago(author here) I don't have anything to add, you guys have great arguments, I just want to show appreciation for starting a good debate based on a code example I did :)
- nikki93 6y agoIt seems like the main benefit the authors of hooks get at is being able to encapsulate a library's logic into hooks that you can reuse, so eg. you could do `const [data, loading] = useQuery('some query');` and the query is aware of lifetime etc. without needing to use mixins or multiple inheritance or traits or something else like the component or ECS paradigms from game engines (that each have their pros and cons). You could have a stateful `this.query` object in your class, but you basically have to spread the code for that around (calling mount / unmount handlers or events) vs. just mentioning it in one place and having it be able to use more hooks internally etc. It's basically like an `import` statement for your component.
- spankalee 6y agoThis is my point about attacking composition with what we have for classes - because reuse and composition are problems not limited to UI widgets. Your query example is easily handled by a mixin: // Queryable provides this.data and this.loading and // hooks into the lifecycle class MyComponent extends Queryable(Component) { query = 'some query'; render() { // ... } } You can also use helper objects: class MyComponent extends Component { query = new Query(this, 'some query'); render() { // ... } connectedCallback() { this.query.connect(); } } I understand that you have to wire up the lifecycle methods this way, but it's at least low-magic and easily understandable. I don't mind it. And even though decorators aren't standard yet, they can be used for composition too. My point is that these problems aren't unique to React and deserve patters and solutions for all JavaScript programming. React is statefull and object-based under the hood, but just trying to hide it.
- gbear0 6y agoI agree with the grandparent that hooks are a weird hack and it drives me nuts every time I have to create one and can't use a closure to precompute some stuff and have better control of the lifecycle on functions/data outside the actual render function. So in the end of all this I've found I much prefer the class syntax cause it just makes more sense and does a much better job (for me) at declaring code patterns and shape than trying to follow code paths in/out of different effects (which oddly enough reminds me of the horror days of following code paths through multiple levels of inheritance). However, there is a major value hooks provide over class components and that's the composability of the lifecycle effects. The problem with class components is that code for a lifecycle is broken across multiple functions and interweaved with other effects. This happens because the declarative model of the component tree clashes with the imperative ordering we code up for lifecycle functions attached 'within' each class component. The fix is to turn lifecycle effects into first level declarative concepts themselves (like in hooks) so they're not 'within' the component, instead they're more of a has-a relationship. This would actually be rather simple to do, drop the lifecycle methods in a component, and add a new Component.addEffect function (or, cause I'm a big fan of decorators, you could have some decorator to declare what effects should be used). And each effect would be another class that inherits from Effect. I think the hardest part to think about is how you interweave the effects like people do in hooks as one output becomes the input for another, but I think this is probably the wrong way to think about it. Instead if you think of it as an Effect tree then each Effect is just a 'render' function where you're passing in other effects as the props and the output is passed down the tree, so you'd just need an inline effect function that would compose 2 effects together the same way we do with components. At this point the conceptual model is extremely easy cause it's declarative trees all the way down! RootComponent |- ComponentA |- effects ||- lambda effect -- myClickEffect || |- myStateEffect ||- lambda effect -- myKeyEffect || |- myStateEffect ||- myCleanupEffect |- children |- ComponentB |- effects |- children |- ComponentC |- effects |- children ... Anyone wanna pass this idea onto the react team that'd be awesome cause I'd love to see this added for Class components to make them more composable as well as having better sharing of ideas between hooks and class components :)
- malisper 6y agoDoesn't this only hand the useState hook? How would this handle something like the useEffect hook where you may want to rerun code on every render?
- deleted 6y ago[deleted]
- wjmao88 6y agoThis syntax might look like it require less magic, but you still have to implement everything react does for hooks that takes care of re-rendering cycles.