RFC: stateful functional components (aka "hooks")
See original GitHub issueReact announced support for hooks.
This idea is an interesting alternative to class-based components, offering many of the same benefits (control state, in particular) while composing considerably better than class-based components.
I’ve argued in the past for some sort of stateful component (because controls have control state that’s only relevant while they exist) and this seems like a nice trade-off, providing a means of maintaining state without providing a complete different concept that often forces people to factor back and forth between stateful and stateless components.
Background
The basic idea is illustrated here:
The difference between class-based and hook-based components is illustrated here:
You can watch Dan Abramov give a full presentation here.
Proposal
I’m proposing only state and effect hooks for now, as these cover a lot of ground. (I’d like to explore the possibility of introducing shared state contexts, and possibly effects, at a later time.)
State Hook
What I’m proposing for state management is slightly different from what the React RFC proposes. It relies on call-order for identity, which seems really brittle - I’m instead proposing that any hook will need to explicitly define in advance the unique states that may be used within a component.
Here is a counter example:
const useCount = createState()
function Counter() {
const [count, setCount] = useCount(0)
return (
<div>
Count: {count}
<button onclick={() => setCount(count + 1)}>+</button>
</div>
)
}
In this example, useCount = createState()
creates a state hook with no initial state, and therefore, the call to the useCount()
function must supply the initial value 0
.
Creating a pre-initialized state hook would also be supported:
const useCount = createState(() => 0)
function Counter() {
const [count, setCount] = useCount()
// ...
}
In this example, useCount = createState(() => 0)
creates a state hook with a built-in initialization function, so the call to useCount()
requires no initial value.
Implementation Notes
The internal functions likely need to change in several ways.
Most importantly, the aggressive calls to functional components in the h()
function need to be deferred, such that a Function
is now regarded (in terms of diffing and patching) as a VNode-type (currently called name
) on par with e.g. "div"
- the actual calls to functional components would need to happen during the diff/patch itself; as late as possible, rather than (as is now) as early as possible.
Since we’ll need to track component instances, we’ll need to store these instances somewhere. Storing them in DOM elements won’t work, since the functional component isn’t an element (and doesn’t contain elements before we invoke the functional component) so I suggest storing them in a Map, since this allows the use of the state hooks (useCount
etc.) themselves as keys.
I’d suggest functional components should support life-cycle callbacks, since keyed updates will become important - if we treat the functional component occurrence like an element in terms of diffing/patching, then key
will come into play, since otherwise we can’t discern component instances.
Likewise, I’d suggest supporting the full range of life-cycle hooks oncreate
, onupdate
, onremove
and ondestroy
for functional component instancse - and since they have no single corresponding DOM element, I’d suggest passing the Map
instance to e.g. oncreate
instead of an HTML element.
As for where to store these Map
instances between renders, I’d suggest the patch()
function return a modified VNode-tree with component instance references. Currently it returns the input VNode-tree exactly as provided - it would likely need to inject the Map
as e.g. vnode.instance
in the returned VNode-tree for subsequent patching/diffing.
I obviously haven’t worked out all the details, so these are just some preliminary ideas.
I’d love to attempt this myself, but I’ve had no luck with this in the past, and I’m pretty sure I’d fail and get frustrated, so for now I’m just posting the idea 😉
Thoughts?
Issue Analytics
- State:
- Created 5 years ago
- Comments:5 (2 by maintainers)
@mindplay-dk Sorted by less abstract to more abstract: Superfine → React → Hyperapp
Superfine is a low-level library. It is only the view layer whereas React is a view layer and a primitive state container, usually enhanced through more advanced state containers (managers) like Redux, Mobx, and so on.
Do Hooks mix with React? Yeah. Do Hooks mix with Superfine? Not really. There’s not even a concept of state in Superfine.
Now, should we introduce a primitive state container to Superfine? I don’t think so. I’m already working on a JavaScript framework that has opinions about views and state management and I’d prefer to keep Superfine as minimal as possible.
Hyperapp+Hooks? That’s another issue. 😉
for instance https://gist.github.com/k1r0s/be41597d9dbe3a8a4ebcaa1131f6a29b