Closed rbiggs closed 6 years ago
@rbiggs Both Superfine and Hyperapp V2 components are eagerly evaluated, thus supporting components that return arrays. Fragment could be implemented by simply returning its children and ignoring its props. The downside is that it doesn't work for the root of your application.
So, the reason you want proper DocumentFragment support is to just be able to avoid a root element?
Please see my related notes here. (scroll back for more context.)
I think this could remove the need for fragments entirely - all components (functional or possibly some kind of stateful component in the future) would essentially expand in-place. Implementing a functional component Fragment
would then just be:
const Fragment = props => props.children
I'm pretty sure the real root of the problem is the aggressive evaluation of functional components in the h
function - at the moment, there's a forced 1:1 parent/child-relationship for functional components, and also, the "real" structure of child VNodes isn't visible to parent components, because any functional child-components have already been evaluated before a functional parent-component executes.
Yup, I'm aware of that for Hyperapp and Superfine. You could just make a simple tag like this:
function Fragment(props) {
return props.children
}
But this type of simple Fragment
tag must be consumed by another component. You can't just render a Fragment tag directly into the DOM. With React you can render a Fragment directly into any container. Notice the difference between these two examples, React first, followed by Superfine:
/////////////////
// React version
// Notice that we can render it directly to the DOM:
/////////////////
function List(props) {
function update() {
list = render(<List data='New Stuff Here' />, document.body)
}
return (
<React.Fragment>
<p>One <input type='checkbox' /></p>
<p>Two</p>
<p>{props.data}</p>
<p>
<button onClick={() => update()}>Change</button>
</p>
</React.Fragment>
)
}
ReactDOM.render(<List data='Three'/>, document.body)
/////////////////////
// Superfine version
// Notice the extra wrapper function needed to consume the Fragment:
/////////////////////
function Fragment(props) {
return props.children
}
function List(props) {
function update() {
list = patch(list, <Container data='New Stuff Here' />, document.body)
}
return (
<Fragment>
<p>One <input type='checkbox' /></p>
<p>Two</p>
<p>{props.data}</p>
<p>
<button onclick={() => update()}>Change</button>
</p>
</Fragment>
)
}
function Container(props) {
return (
<div>
<List data={props.data} />
</div>
)
}
let list = patch(null, <Container data='Three'/>, document.body)
Now let's say you want to hydrate some server rendered content that happens to be a defintion list (DL). You've written a tag that returns the new list items:
function
function Glossary(props) {
return (
{
props.items.map(item => (
// Without the `key`, React will fire a key warning
<Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</Fragment>
))
}
);
}
ReactDOM.render(<Rows items={newData}/>, document.querySelector('dl'))
The above example can easily hydrate a definition list with new entries. If we try to do that with Superfine, we'd need to wrap the dt
and dd
in another tag, breaking the definition list markup. No way around that. Hydrating existing tables could have similar problems. Now if you're a full stack developer, you can go and change the server rendered markup. If you're not, you've got a problem.
That said, I've never needed to render a Fragment tag directly into the DOM, so 🤷 This was just an attempt to try and reproduce the functionality of React Fragments with the latest Superfine. I though the new VNode
flags would make it easier to pull off, which they did. But I'm having difficulty figuring out how to get Fragment children to update efficiently instead of re-render all of them.
Here's the documentation for the Fragment tag
And here's a Codepen of a React Fragment.
@mindplay-dk You point about h
function:
the real root of the problem is the aggressive evaluation of functional components in the h function
But I'm not sure how to change that, which forced me to seek other means to pull it off.
@rbiggs Patching fragments can become messy and I don't think it's worth adding this complexity, at least not now. I have no plans to change how h
eagerly evaluates components at the moment, so there is little to gain here, but I am willing to be proven wrong.
I know. This was more of an experiment to see how far I could get to have same support as React.Fragment. 🏄♂️
I forked Superfine and created a branch so I could give a shot at adding support for React-style Fragment tag. I've got a preliminary Fragment tag that can be inserted into the DOM, even
document.body
, or any other container element. You can also update the Fragment's contents. Here's the branch: https://github.com/rbiggs/superfine/tree/Fragment-Support.And here's a working Codepen: https://codepen.io/rbiggs/pen/GGVzRY?editors=0010
If you scroll down to the bottom of the README you'll see the documentation for how to use the Fragment tag.
This branch includes a new tag:
Fragment
that creates a new VNode type:FRAGMENT
. I also had to update both thecreateElement
andremoveElement
functions to work with the Fragment VNode.What's Missing
If you try out the example on Codepen, you'll notice that when you update the Fragment tag, all of its children are rerendered instead of only the changed child. I'm not exactly sure how to check whether a Fragment's children have changed. Any suggestions from those more familiar with the inner works of Superfine are greatly appreciated. @jorgebucaran, do you have any idea how to resolve that? With React 16, only the Fragment child that needs to be updated gets updated.
TODO: The current tests would need to be updated for these changes, particularly those made to
createElement
andremoveElement
.