This is part 3 of 3 in a series titled "How do we React?" in which I conclude our discussion of how Aurelia handles common React scenarios.
The other week Sebastian Markbåge posted the following tweet :
You may have noticed that most other frameworks don't have HoCs, render props or anything like React.Children. These account for a lot the differences between React and other frameworks. How would you solve these use cases if you had to switch to [other framework]?
In this post, we'll cover React.Children For a discussion of how Aurelia handles typical HoC scenarios, please see part 1 or for information on how we handle render props, see part 2.
React.Children
From Sebastian's original tweet, it wasn't clear to me whether he was referring to the
React.Children
helper or to a React component's
props.children
. The utility seems less essential to me, so I'll focus primarily on
props.children
here, but I'll also make a few notes relating to React.Children
.
The React site states :
Some components don’t know their children ahead of time. This is especially common for components like
Sidebar
orDialog
that represent generic "boxes". We recommend that such components use the specialchildren
prop to pass children elements directly into their output.
Slots
If you've read part 2 of this series, then you may have already realized that this is handled quite simply by Aurelia's adoption of the Shadow DOM web standard. As an example, the FancyBorder
component shown on the React site can be authored in Aurelia like this:
<template bindable="color" class="fancy-border-${color}">
<slot></slot>
</template>
In fact, we don't even need to write any JavaScript at all to implement this. Because these types of container components are so common, Aurelia provides a mechanism for creating HTML-only components, which is what I've shown above. Here's how it's used:
<fancy-border color="red">
Hello from Aurelia!
</fancy-border>
As a reminder, or if you aren't familiar with Shadow DOM, the HTML child nodes of the fancy-border
element will automatically be projected into the location of the slot
element. That's all there is to it.
Because we've adopted the Shadow DOM slot composition model, we can also declaratively use named slots, slot fallback content, and re-project children from a slot in one element to a slot in a nested component. All that comes for free.
Web Standards
A core value of Aurelia has always been to be as standards compliant as possible. Besides the natural benefit of working with browsers rather than against them, anyone who knows the standards already knows most of Aurelia. And those who are less familiar with the standards will learn many of them while using Aurelia, which is an excellent career side-effect for any front-end engineer.
The @children Decorator
For the most part, something like React.Children isn't needed in Aurelia, since developers are typically working with slot-based composition or simple collections of plain JavaScript objects.
That said, you may wonder how a container component would gain access to its child components directly, so that it can manipulate them, query them, or build other UI based on them. If you have that scenario in Aurelia, all you need to do is decorate a property with the @children
decorator. You can optionally specify a CSS selector, to filter which children you want to work with. For example, if you were building a tab control, you could do something like this:
export class TabControl {
@children('tab-item') items;
selectTab(item) {
// ellided
}
}
With this in place, you have a live list of all the component view models for tab-item
components that are children of a tab-control
. So, you could build a view for the tab-control
like this:
<template>
<ul class="tab-strip">
<li repeat.for="item of items">
<a click.trigger="selectTab(item)">${item.heading}</a>
</li>
</ul>
<div class="tab-items">
<slot></slot>
<div>
</template>
With the view above, the tab-item
components will be rendered at the location of the slot
. However, in addition to that, an li
will be rendered for every tab-item
child, with the text content of the a
set to the heading
property of the item.
In other words, you could write this...
<template>
<tab-control>
<tab-item heading="Tab 1">
Content of tab 1.
</tab-item>
<tab-item heading="Tab 2">
Content of tab 2.
</tab-item>
<tab-item heading="Tab 3">
Content of tab 3.
</tab-item>
</tab-control>
</template>
And it would render something like this:
<tab-control>
<ul class="tab-strip">
<li>
<a>Tab 1</a>
</li>
<li>
<a>Tab 2</a>
</li>
<li>
<a>Tab 3</a>
</li>
</ul>
<div class="tab-items">
<tab-item heading="Tab 1">
Content of tab 1.
</tab-item>
<tab-item heading="Tab 2">
Content of tab 2.
</tab-item>
<tab-item heading="Tab 3">
Content of tab 3.
</tab-item>
<div>
</tab-control>
Because Aurelia's templating and binding systems are deeply integrated, you could drive the entire tab control with data also, like this:
<template>
<tab-control>
<tab-item repeat.for="n of 3" heading="Tab ${n}">
Content of tab ${n}.
</tab-item>
</tab-control>
</template>
This would result in the same final HTML composition as the static version above.
Wrapping Up
This post was quite a bit shorter than part 2, primarily because much of what we covered in part 2 also addresses the scenarios for React.Children
and props.children
. Aurelia's standards-based slot composition model, combined with powerful decorators like @children
enable a very declarative model for what would otherwise be complex UI patterns.
We hope you've enjoyed this three part series and now have a better idea of how Aurelia provides its own approaches to various React scenarios. If you're curious to see what Aurelia vNext has in store, or to learn more about Aurelia and how it compares to other frameworks, please subscribe to this blog. There's much more goodness to come.
Cheers!