はじめに
ReactのJSXについて、なんとなく使っている感があったので改めて整理してみました。
JSXってなんなの?
ReactのcreateElementをhtmlライクにかけるようにするものです。
JSXはBabelによって最終的にcreateElementに変換されるようです。
babel-plugin-transform-react-jsxで変換を行なっていそうです。
// Builds JSX into:
// Production: React.createElement(type, arguments, children)
// Development: React.createElement(type, arguments, children, source, self)
function buildCreateElementCall(
path: NodePath<JSXElement>,
file: PluginPass,
) {
const openingPath = path.get("openingElement");
return call(file, "createElement", [
getTag(openingPath),
buildCreateElementOpeningElementAttributes(
file,
path,
openingPath.get("attributes"),
),
// @ts-expect-error JSXSpreadChild has been transformed in convertAttributeValue
...t.react.buildChildren(path.node),
]);
}
このbabelプラグインではJSXFragment、JSXElement、JSXAttributeについて変換を行なっています。
JSXFragment/JSXElement
JSXFragmentは<Fragment>(<>...</>)
の構文で使用されるものです。
JSXElementは<div>
のようなhtml風のelementです。
変換の中は似たような動きをしているのでいっぺんに紹介します。
JSXFragment: {
exit(path, file) {
let callExpr;
if (get(file, "runtime") === "classic") {
callExpr = buildCreateElementFragmentCall(path, file);
} else {
callExpr = buildJSXFragmentCall(path, file);
}
path.replaceWith(t.inherits(callExpr, path.node));
},
},
JSXElement: {
exit(path, file) {
let callExpr;
if (
get(file, "runtime") === "classic" ||
shouldUseCreateElement(path)
) {
callExpr = buildCreateElementCall(path, file);
} else {
callExpr = buildJSXElementCall(path, file);
}
path.replaceWith(t.inherits(callExpr, path.node));
},
},
runtimeがclassicかautomaticかで変わるようです。デフォルトはautomaticのようです。
classicの場合、JSXで記述されたコードは直接React.createElement
に変換されます。
automaticの場合、JSXで記述されたコードは_jsx
や_jsxs
に変換されます。
React.createElement
に変換されるのと、_jsx
に変換される時の挙動の違いとして、_jsx
はJSXの記載時にimport React from 'react';
の記述が不要になります。詳しくはは新しい JSX トランスフォームに載っています。
buildJSXFragmentCallやbuildJSXElementCallの延長で小要素を取得して_jsx
に置き換えをしています。
JSXAttribute
JSXAttributeはJSXで記述した際の属性を示しています。<a href={linkTarget()}>link</a>
とかでいうとhref={linkTarget()}
がattributeです。
JSXAttribute(path) {
if (t.isJSXElement(path.node.value)) {
path.node.value = t.jsxExpressionContainer(path.node.value);
}
},
JSXAttributeのnodeがJSXElementの場合、jsxExpressionContainer
でvalidationをかけて、結果をnodeに格納しているようです。
NodeTypeがExpressionかJSXEmptyExpressionであればvalidationがpassしそうです。
ExpressionはJavascriptの式であれば基本的にExpressionに当たるようです。JSXEmptyExpressionはJSXの中括弧 {} の間に何もない場合に生成されるようです。
以下のようなコードの{ }
がそれに当たります。
<>
{ }
</>
結果、JSXAttributeはajavascriptの式か、空の式であるかどうかをチェックして、問題がなければそのまま値を格納するような動きをしていそうです。
おわりに
ここまでで一通りJSXの変換の断片を見てきました。
JSXはBabelによってJSXFragment/JSXElement/JSXAttributeなどに分類され、ツリー状のASTとして表現されます。
JSXFragment/JSXElementの場合は_jsx
(その先はcreateElement)または、createElementに変換され、JSXAttributeはvalidationのみをされていることがわかりました。
また、runtimeをautomaticに設定することで、直接createElementに変換されず、_jsx
に変換されるため、import React from 'react';
を記述することなくJSXの構文で記述できることがわかりました。
次はcreateElementについて読んでみていきたいと思います。