| JavaScript XML | |
|---|---|
| パラダイム | ドメイン固有言語、言語拡張 |
| 家族 | ECMAScript、XML |
| デザイン: | フェイスブック |
| 開発者 | フェイスブック |
| 初登場 | 2014年8月29日[ 1 ] ( 2014年8月29日) |
| タイピングの規律 | 弱い、ダイナミック |
| ファイル名拡張子 | .jsx、.tsx |
| Webサイト | github.com /facebook |
| 主要な実装 | |
| バベル | |
| 方言 | |
| 反応する | |
JSX ( JavaScript XMLと呼ばれることもある)は、JavaScript言語構文のXMLのような拡張です。[ 2 ]当初はFacebookがReactで使用するために作成しましたが、JSXは複数のウェブフレームワークに採用されています。[ 3 ] : 5 [ 4 ] : 11 構文糖衣であるJSXは、通常、元のJSXと構造的に類似したネストされたJavaScript関数呼び出しにトランスパイルされます。
TypeScriptで使用する場合、ファイル拡張子は.tsxになります。[ 5 ]
JSXで書かれたコードは、Webブラウザで理解できるようになる前に、Babelなどのツールで変換する必要がある。 [ 6 ] [ 7 ]:5 この処理は通常、アプリケーションがデプロイされる前のソフトウェアビルドプロセス中に実行される。
トランスパイラ(Babelなど)は、JSXの使用の性質について仮定を立てなければなりません。そうでなければ、JSXタグを何に変換すればよいか分かりません。Babelの場合(下記)、タグがreactで使用されることを前提としたモードで使用するため、JSXタグはReact JSXランタイムへの関数呼び出しに変換され、これらのタグの内部表現に対応する値を返します。[ 8 ] BabelがJSXに使用するは、と@babel/plugin-transform-react-jsxの役割を担う代替プラグマを指定できるようにすることで、他のプラットフォームに適応するための柔軟性を提供しています。[ 8 ]React.createElementReact.Fragment
次の例には、React-JSX/TSX コードと Babel からのトランスパイルされた出力の両方が含まれています。
| TSXコード | トランスパイルされた出力(Babel) |
|---|---|
'react'からReactをインポートします。const App : React.FC = () => { return ( < div > < p >ヘッダー</ p > < p >コンテンツ</ p > < p >フッター</ p > </ div > ); }デフォルトアプリをエクスポートします。 | import React from 'react' ; import { jsx as _jsx , jsxs as _jsxs } from "react/jsx-runtime" ; const App = () => { return /*#__PURE__*/ _jsxs ( "div" , { children : [ /*#__PURE__*/ _jsx ( "p" , { children : "Header" }), /*#__PURE__*/ _jsx ( "p" , { children : "Content" }), /*#__PURE__*/ _jsx ( "p" , { children : "Footer" })] }); }; export default App ; |
Babelによって追加されたコメント/*#__PURE__*/は、タグ付けされた呼び出しが純粋関数への呼び出しであることを示しています。これは、ツリーシェイキングなどの後のステップに関係しています。[ 9 ]これはコードの機能には必要ないため、次の例では読みやすくするためにこの注釈が削除されています。
上記のトランスパイラ出力はReact 17以降からの出力を示しています。それ以前のバージョンでは、出力はより単純なものになります。
const App = () = > { return React.createElement ( "div" 、null 、React.createElement ( "p" 、null 、"Header" )、React.createElement ( " p" 、null 、" Content" )、React.createElement ( " p " 、null 、" Footer" )) ; } ; export default App ;クラシック インターフェースは、React 以外の JSX ユーザーによって引き続き広く使用されています。
同じレベルにある複数の要素は、上記の要素のように単一の要素で囲むか、またはその省略形で<div>区切られたフラグメント、または配列として返す必要があります。[ 10 ] [ 11 ] [ 4 ]:68–69 <Fragment><>
JSXは、HTMLが提供する属性を反映するように設計された様々な要素属性を提供します。カスタム属性をコンポーネントに渡すこともできます。[ 12 ]すべての属性は、コンポーネントによってprops(プロパティ)として受け取られます。
有効なJSX属性名は有効なJSX識別子(変数名)であり、ECMAScript識別子との主な違いは、-先頭以外の位置に(ハイフン-マイナス)文字を追加で使用できることです。これにより、 や などのダッシュを含むHTML属性をdata-*容易aria-roleに使用できます。無効な文字がまだ含まれている属性は、構文に従って記述できます{... spread}。[ 2 ]
| TSXコード | トランスパイルされた出力(Babel) |
|---|---|
< a href = "https://en.wikipedia.org" data-note = "Wikipedia" { ... { ": tricky - property - name ": a }} // XMLプロパティにはコロンが含まれ、コロンで始まる場合があります> Wikipedia </ a >; | React.createElement ( "a" , { href : "https://en.wikipedia.org" , "data - note" : "Wikipedia" , ":tricky-property-name" : a , }, " Wikipedia" , ); |
カスタム「タグ」(コンポーネント) は、特定の呼び出しスタイルを受け入れる JavaScript 関数です。
| JSXコード | トランスパイルされた出力(Babel) |
|---|---|
クラスHello はComponentを拡張します{ render () { return < h1 > Hello , { this . props . name } ! </ h1 >; } }function Jello ({ name }) { return < h1 > Jello , { name } ! </ h1 >; }エクスポートデフォルト関数App () { return ( <> < Hello name = "Sarah" /> < Jello name = "Connor" /> </> ); } | クラスHello はComponentを拡張します{ render ( ) { return React.createElement ( "h1" , null , " Hello, " , this.props.name , " ! " ) ; } }function Jello ({ name }) { return React . createElement ( "h1" , null , "Jello, " , name , "!" ); }function App () { return React.createElement ( React.Fragment , null , React.createElement ( Hello , { name : " Sarah" , } ) , React.createElement ( Jello , { name : " Connor " , } ) , ) ; } |
ここではname="Connor"単にオプションになります{ name: "Connor" }。
childrenラップ コンポーネントを含むコンポーネントは、トランスパイルされた出力ですでに確認した属性 を使用して定義できます。
| JSXコード | トランスパイルされた出力(Babel) |
|---|---|
const Wrapper = ( props ) => { return ( < div style = {{ backgroundColor : 'lightgray' , padding : '20px' , border : '1px solid black' }}> < h2 >ラッパー内部: </ h2 > { props . children } </ div > ); } ; < Wrapper > < p >段落</ p > < p >もう1 つ! </ p > </ Wrapper > | const Wrapper = ( props ) => { return React.createElement ( "div" , { style : { backgroundColor : "lightgray" , padding : "20px" , border : "1px solid black" , } , }, React.createElement ( "h2" , null , "ラッパー内部: " ) , props.children , ) ; } ;React.createElement ( Wrapper 、null 、React.createElement ( "p" 、null 、"段落" )、React.createElement ( " p" 、null 、"もう1 つ!" ) 、); |
有効なJSXタグ名は有効なJSX識別子(変数名)であるため、ハイフンで区切られたXMLタグやHTMLタグも問題なく使用できます。ただし、ECMAScriptの文法上の制限により、カスタムコンポーネントの名前にはハイフンを使用できません。[ 2 ]
JavaScriptの式(文ではない)は、JSX内で中括弧を使って使用できます{}: [ 4 ] : 14–16
| JSXコード | トランスパイルされた出力(Babel) |
|---|---|
< h1 >{ 10 + 1 } h1 > | React.createElement ( "h1 " 、null 、10 + 1 ) ; |
上記の例では次のようにレンダリングされます:
< h1 > 11 </ h1 >JSX内ではif-else文は使用できませんが、代わりに条件式を使用できます。以下の例は、が1に等しい ため、文字列としてレンダリングされます。{i===1?'true':'false'}'true'i
| TSXコード | トランスパイルされた出力(Babel) |
|---|---|
'react'からReactをインポートします。const App : React.FC = () => { const i : number = 1 ;戻り値( < div > < h1 > { i === 1 ? 'true' : 'false' h1 > </ div > ); }デフォルトアプリをエクスポートします。 | 'react'からReactをインポートします。const App = ( ) => { const i = 1 ; return ( React . createElement ( "div" 、null 、React . createElement ( "h1" 、null 、i === 1 ? "true" : "false" ) ) ; }; export default App ; |
上記は次のようにレンダリングされます:
< div > < h1 >真</ h1 > </ div >関数とJSXは条件文で使用できます: [ 4 ] : 88–90
| TSXコード | トランスパイルされた出力(Babel) |
|---|---|
'react'からReactをインポートします。const App : React.FC = () => { const sections : number [] = [ 1 , 2 , 3 ];return ( < div > { sections . map (( n , i ) => ( /* 'key' はリスト項目とその変更を追跡するための React 固有の属性です */ /* 各 'key' は一意である必要があります */ < div key = { "section-" + n }> Section { n } { i === 0 && < span >( first )</ span >} </ div > ))} </ div > ); }デフォルトアプリをエクスポートします。 | 'react'からReactをインポートします。const App = () = > { const sections = [ 1 , 2 , 3 ] ; return React.createElement ( "div" , null , sections.map ( ( n , i ) = > React.createElement ( "div" , { key : "section-" + n } , "Section " , n , " " , i === 0 && React.createElement ( "span" , null , " (first)" ) , ) , ), ); };デフォルトアプリをエクスポートします。 |
上記は次のようにレンダリングされます:
< div > < div >セクション 1 < span > (最初) </ span ></ div > < div >セクション 2 </ div > < div >セクション 3 </ div > </ div >Babelなどのトランスパイラは、React以外の関数呼び出しをターゲットにできる場合が多いです。ここではBabelは次のように呼び出しtag、frag代わりに次のように指示されます。
| TSXコード | トランスパイルされた出力(Babel) |
|---|---|
// @jsx タグ// @jsxfrag frag const App = () => { const sections = [ 1 , 2 , 3 ]; return ( < div > { sections . map (( n , i ) => ( < div key = { "section-" + n }> Section { n } { i === 0 && < span >( first )</ span >} </ div > ))} </ div > ); }; | // @jsx タグ// @jsxfrag frag const App = () => { const sections = [ 1 , 2 , 3 ]; return tag ( "div" , null , sections . map (( n , i ) => tag ( "div" , { key : "section-" + n }, "Section " , n , " " , i === 0 && tag ( "span" , null , "(first)" )), ), ); }; |
と の異なるバージョンを使用するとtag、frag入力の異なる表現を生成できます。
| JSコード | プログラム出力 |
|---|---|
// オブジェクト ツリー。//フラグメントは配列として表されます。function frag (... children ) { return children ; }// タグはオブジェクトとして表現されますfunction tag ( name , attrs , ... children ) { // 配列をフラット化するとフラグメントが展開されます// それでも "false" を処理する必要がありますconst kids = [] for ( x of children . flat ( Infinity )) { if ( x === false || x === null || x === undefined ) { if ( kids . at ( - 1 ) === " " ) kids . pop () } else kids . push ( x ) } return { name , children : kids , ...( attrs && { attrs })} }console.log ( JSON.stringify ( App ( ) ) ) ; | { "name" : "div" , "children" : [ { "name" : "div" , "attrs" : { "key" : "section-1" }, "children" : [ "セクション " , 1 , " " , { "name" : "span" , "children" : [ "(first)" ] } ] }, { "name" : "div" , "attrs" : { "key" : "section-2" }, "children" : [ "セクション " , 2 ] }, { "name" : "div" , "attrs" : { "key" : "section-3" }, "children" : [ "セクション " , 3 ] } ] } |
// Pug のような出力関数frag (... children ) { let kids = []; for ( const x of children ) { // テキストは | で始まるif ( ! Array . isArray ( x )) kids . push ( "| " + x ); else kids . push (... x . flat ( Infinity )); } }function tag ( name , attrs , ... children ) { const indent = " " ; const attrStr = Object . entries ( attrs || {}) . map (([ k , v ]) => ` ${ k } =" ${ v } "` ) . join ( ", " ); const head = ` ${ name }${ attrStr ? `( ${ attrStr } )` : "" } ` ; let kids = []; for ( const x of frag ( children )) { if ( x === false ) { if ( kids . at ( - 1 ) === "| " ) kids . pop (); } else kids . push ( x ); } if ( kids . length == 1 ) // elem>text および要素のネストのための省略形構文return kids [ 0 ]. startsWith ( "| " ) ? [ ` ${ head } ${ kids [ 0 ] . substr ( 2 ) } ` ] : [ ` ${ head } : ${ kids [ 0 ] } ` ]; return [ head , ... kids . map (( line ) => indent + line )]; }コンソール.log ( App (). join ( '\n' ) ); | div div (キー= "section-1" ) | セクション | 1 | span (最初) div (キー= "section-2" ) | セクション | 2 div (キー= "section-3" ) | セクション | 3 |
上記はJSXからECMAScriptコードへの変換について説明しています。「プラグマ」セクションで示されているように、JSX派生オブジェクトを受け取るECMAScriptコードは、これらのデータに対して自由に処理を行うことができます。
HTML と XML が関係する限り、相違が最もよく見られる領域は、属性の処理と利用可能なコンポーネントの設定です。
JSXは属性の解析方法を定義し、Babelはそれを他のECMAScriptコードにどのように伝えるかについての規約を維持していますが、これらの属性が実際のHTMLおよびXML属性にどのようにマッピングされるかは定義していません。例えば、Reactは{... spread}構文の必要性を最小限に抑えるために、以下の規約を使用しています。[ 15 ]
data-*HTMLのハイフンでつながれた属性名は、とを除き、キャメルケースに変換されますaria-*。これはJavaScript DOM APIに準拠しています。xlink:*名は camelCase に変換されます。たとえば、xink:hrefになりますxlinkHref。ReactのDOMレンダラーには、認識可能な属性のプリセットリストがあります。これらの属性の一部はHTML/XMLへの変換用であり、その他は内部使用用です。本番環境(非開発環境)では、タグが「svg」または「math」でない限り、出力タグにはこれらの属性のみを使用できます。[ 16 ]
HTML属性は、それを持つ要素にCSSstyleスタイルのコレクションを適用します。人間工学的な配慮として、React-DOMはこの値を文字列形式だけでなくJavaScriptオブジェクトとしても渡すことができます。 の代わりにと記述することもできます。「style」関数の実装により、オブジェクトは自動的に適切な文字列形式に変換されます。同様のキャメルケースへの名前変更操作は、ハイフンで区切られた名前を持つスタイルの使用を簡素化するために行われます。[ 17 ]<div style={"color:" + clr}><div style={{ color: clr }}>
React-DOMでは、イベントハンドラ属性の値として文字列ではなくJavaScript関数を渡すこともできます。[ 17 ]
出力言語に応じて、HTML/XMLの「タグ」のように表示される異なるコンポーネントが利用できる場合があります。例えば、ReactのHTMLタグは から取得されますがreact-dom、これは異なる言語をターゲットとする場合は使用されません。ランタイムはいくつかのコンポーネントを事前定義する場合もあります。<Fragment>はその一例ですが、Babelの規約により汎用化されています。[ 18 ]