JSX is the starting point
React uses JSX to make things easier for the developers. So when you write something like this.
<div id="foo">
Hello!
</div>
Babel with a react preset transforms it to this.
React.createElement("div", {
id: "foo"
}, "Hello!");
Check out this example in Babel REPL.
React.createElement
is a function that creates a virtual node.
It's a well-known fact, and you probably already know it. So what's the point?
Preact way
If you've used Preact before, you may notice it has an unobvious export in its source code.
export {
createElement,
createElement as h,
} from './create-element';
To make things clear, the createElement
function from Preact serves the same needs as React.createElement
. So the question is, why is it exported as h
as well?
The reason is dead simple. It's exported as h
because it's a hypescript function.
So what exactly is hypescript?
Hyperscript is the key
Hypescript is a kind of language to create HyperText with JavaScript and was started by Dominic Tarr in 2012. He was inspired by markaby, the "short bit of code" to write HTML in pure Ruby. Markaby allows doing things like that.
require 'markaby'
mab = Markaby::Builder.new
mab.html do
head { title "Boats.com" }
body do
h1 "Boats.com has great deals"
ul do
li "$49 for a canoe"
li "$39 for a raft"
li "$29 for a huge boot that floats and can fit 5 people"
end
end
end
puts mab.to_s
And the h
function allows doing essentially the same thing, but with different syntax.
h = require("hyperscript")
h("div#foo", "Hello!")
It also supports nesting and CSS properties.
h = require("hyperscript")
h("div#foo",
h("h1", "Hello from H1!", { style: { 'color': 'coral' } })
)
Check out an interactive demo to see how it works.
Get your hands dirty
Now when we know what the h
function does and why we need it, let's write our own version of it. Complete example can be found on codesanbox.
First, let's make up a render
function, that creates real DOM elements from our virtual nodes.
const render = ({type, children, props}) => {
const element = document.createElement(type);
if (props) {
for (const prop in props) {
element.setAttribute(prop, props[prop]);
}
}
if (children) {
if (Array.isArray(children)) {
children.forEach(child => {
if (typeof child === 'string') {
element.innerText = child;
} else {
element.appendChild(render(child));
}
})
} else if (typeof children === 'string') {
element.innerText = children;
} else {
element.appendChild(render(children));
}
}
return element;
}
Than, let's create the h
function.
const h = (type, children, props) => {
let handledType = typeof type === 'string' ? type : 'div';
return {
type: handledType,
props,
children
}
}
Finally, let's create an actual content with our h
function, render it with our render
function and mount the result to the DOM.
const div = render(
h('div',
[
h('h1', 'Hello!', { id: 'foo' }),
h('h2', 'World!', { class: 'bar' })
],
)
);
document.querySelector('#app').appendChild(div);
I'm looking forward to similar articles, what should I do?
First of all, if you do like this post leave a reaction or/and a comment to let me know, that I am going in the right direction. I really encourage you to leave any constructive feedback, either positive or negative.
If you want more content like this right now:
- Check out my article on why you have to use
className
instead ofclass
in React components. - Check out my series, where we take a deep dive into React codebase.
If you want more content like this next week:
- Follow me on hashnode, I'll try to do my best to post an article every week there or even make a special hashnode-exclusive series.
- Follow me on Twitter, if you want to know about every article I made and also read their sum-ups in threads.
- Follow me on dev.to, I am going to post the next episode of the Deep-dive-into-React-codebase series on January 23 (next Sunday!).