Saturday, August 13, 2022

How JSX Gets Compiled to Javascript


With JSX, the compiler turns each tag into a function call, so a <div> tag becomes something like a call to createElement() that creates the div in the DOM. It doesn't directly call document.createElement, instead it's calling React's version of createElement, called: React.createElement().
Here is an example:
<div>Hello!</div>
Compiles to:
React.createElement("div", null, "Hello!");
You can try this out for yourself at: https://babeljs.io/repl

Just type in your JSX in the left hand pane, and you'll see the compiled code to the right. Furthermore, the child nodes of a tag get turned into additional parameters to React.createElement.
Example:
<div>
  <h1>Hello!</h1>
  <p>My paragraph.</p>
</div>
Compiles to:
React.createElement(
  "div",
  null,
  React.createElement("h1", null, "Hello!"),
  React.createElement("p", null, "My paragraph.")
);
This explains why you can only write a subset of Javascript within JSX. Because you are really writing code that can go within the argument list to the createElement function call. Some things work within an argument list and some don't. A regular if statement won't work. You'll get a syntax error at the "if":
let showParagraph = false;
React.createElement(
  "div",
  null,
  React.createElement("h1", null, "Hello!"),
  if (showParagraph) { React.createElement("p", null, "My paragraph.") }  // Syntax Error
);
But a ternary operator will work just fine:
let showParagraph = false;
React.createElement(
  "div",
  null,
  React.createElement("h1", null, "Hello!"),
  showParagraph ? React.createElement("p", null, "My paragraph.") : ''
);
And a short circuit evaluation will work as well:
let showParagraph = false;
React.createElement(
  "div",
  null,
  React.createElement("h1", null, "Hello!"),
  showParagraph && React.createElement("p", null, "My paragraph.")
);
Also, React will accept arrays of created elements as parameters:
const paragraphs = [
  'My first paragraph',
  'The second paragraph',
];
  <div>
  <h1>All My Paragraphs</h1>
  { paragraphs.map(txt => <p>{txt}</p>) }
</div>
Compiles to:
const paragraphs = ["My first paragraph", "The second paragraph"];

React.createElement(
    "div",
    null,
    React.createElement("h1", null, "All My Paragraphs"),
    paragraphs.map((txt) => React.createElement("p", null, txt))
);
This also explains why you generally don't put semi-colons into JSX: It's because you don't put semi-colons into function argument lists when calling a function, and with JSX, you are really calling a function with every tag name.

Another thing to note is that a sequence of JSX code gets compiled to a single expression (i.e. a function call to React.createElement with a bunch of parameters). That means if you have an arrow function that's returning JSX code, you don't need to write it with surrounding curly braces and a return statement. So the following:
const topics = [
  {heading: 'First Topic', text:'My first topic text'},
  {heading: 'Second Topic', text: 'The second topic text'},
];
{ topics.map(t => {
  return <div>
            <h2>{t.heading}</h2>
            <p>{t.text}</p>
         </div>
  })
}
Can be re-written as:
const topics = [
  {heading: 'First Topic', text:'My first topic text'},
  {heading: 'Second Topic', text: 'The second topic text'},
];
{ topics.map(t => <div>
                    <h2>{t.heading}</h2>
                    <p>{t.text}</p>
                  </div>
  )
}
Which compiles to:
const topics = [{
  heading: 'First Topic',
  text: 'My first topic text'
}, {
  heading: 'Second Topic',
  text: 'The second topic text'
}];
{
  topics.map(t => React.createElement(
	"div", 
	null, 
	React.createElement("h2", null, t.heading), 
	React.createElement("p", null, t.text)
  ));
}
The key thing to note here is that the outer div becomes a function call, and all the other markup within that div become paremeters to that single, outer function call. The h2 and p elements do generate function calls to React.createElement, but those calls are done as part of the parameter list to the outer function (the div).

The result is that we have just one outer function call, with everything else being a parameter to that function. So, we have a single expression.

However, if you need to write more complex logic that can't be written as a single expession in the arrow function, then you'll have to put the curly braces and return statement back in. For example:
const topics = [
  {heading: 'First Topic', text:'My first topic text'},
  {heading: 'Second Topic', text: 'The second topic text'},
];
{ topics.map(t => {
    if (t.heading === 'Second Topic') {
      t.text += ' (this is extra text for the second topic)';
    }
    return <div>
              <h2>{t.heading}</h2>
              <p>{t.text}</p>
           </div>
    }
  )
}
Here the extra "if" statement added to the arrow function means the function body is no longer a single expression, so we need to wrap the function body in curly braces, and put in the return statement.

Image: Joshua Sortino, CC-BY-SA 3.0

Post a Comment

Note: Only a member of this blog may post a comment.