JSX は、埋め込み可能なXML風の構文です。有効なJavaScriptに変換されることを意図していますが、その変換のセマンティクスは実装固有です。JSXはReact フレームワークで人気が高まりましたが、それ以降、他の実装も登場しています。TypeScriptは、JSXの埋め込み、型チェック、およびJavaScriptへの直接コンパイルをサポートしています。
基本的な使い方
JSXを使用するには、2つのことを行う必要があります。
- ファイルに
.tsx
拡張子を付ける jsx
オプションを有効にする
TypeScriptには、preserve
、react
、react-native
の3つのJSXモードが付属しています。これらのモードは、出力ステージのみに影響し、型チェックには影響しません。 preserve
モードは、JSXを出力のままにして、別の変換ステップ(例: Babel)でさらに処理できるようにします。さらに、出力には.jsx
ファイル拡張子が付きます。 react
モードはReact.createElement
を出力し、使用する前にJSX変換を行う必要はなく、出力には.js
ファイル拡張子が付きます。 react-native
モードは、すべてのJSXを保持するという点でpreserve
と同等ですが、出力には代わりに.js
ファイル拡張子が付きます。
モード | 入力 | 出力 | 出力ファイル拡張子 |
---|---|---|---|
preserve |
<div /> |
<div /> |
.jsx |
react |
<div /> |
React.createElement("div") |
.js |
react-native |
<div /> |
<div /> |
.js |
react-jsx |
<div /> |
_jsx("div", {}, void 0); |
.js |
react-jsxdev |
<div /> |
_jsxDEV("div", {}, void 0, false, {...}, this); |
.js |
jsx
コマンドラインフラグ、またはtsconfig.jsonファイルの対応するオプションjsx
を使用して、このモードを指定できます。
*注:react JSX出力の対象となるJSXファクトリ関数は、
jsxFactory
オプション(デフォルトはReact.createElement
)で指定できます。
as
演算子
型アサーションの記述方法を思い出してください
ts
const foo = <foo>bar;
これは、変数bar
がfoo
型を持つことをアサートします。TypeScriptは型アサーションにも山かっこを使用するため、JSXの構文と組み合わせると、特定の解析が困難になります。そのため、TypeScriptは.tsx
ファイルでの山かっこ型アサーションを許可しません。
上記の構文は.tsx
ファイルでは使用できないため、代替の型アサーション演算子であるas
を使用する必要があります。この例は、as
演算子を使用して簡単に書き直すことができます。
ts
const foo = bar as foo;
as
演算子は、.ts
ファイルと.tsx
ファイルの両方で使用でき、山かっこ型アサーションスタイルと同じ動作をします。
型チェック
JSXでの型チェックを理解するには、まず組み込み要素と値ベースの要素の違いを理解する必要があります。JSX式<expr />
が与えられた場合、expr
は環境に組み込まれているもの(例:DOM環境でのdiv
またはspan
)または作成したカスタムコンポーネントのいずれかを指します。これは2つの理由で重要です。
- React では、組み込み要素は文字列として出力されます(
React.createElement("div")
)。一方、自分で作成したコンポーネントは文字列として出力されません(React.createElement(MyComponent)
)。 - JSX 要素に渡される属性の型は、異なる方法で調べられる必要があります。組み込み要素の属性は*本質的に*既知であるべきですが、コンポーネントは独自の属性セットを指定したい場合があります。
TypeScript は、これらの要素を区別するために React と同じ規則を使用します。組み込み要素は常に小文字で始まり、値ベースの要素は常に大文字で始まります。
組み込み要素
組み込み要素は、特別なインターフェース JSX.IntrinsicElements
で検索されます。デフォルトでは、このインターフェースが指定されていない場合、何でも許可され、組み込み要素の型チェックは行われません。ただし、このインターフェースが*存在する*場合、組み込み要素の名前は JSX.IntrinsicElements
インターフェースのプロパティとして検索されます。例えば
ts
declare namespace JSX {interface IntrinsicElements {foo: any;}}<foo />; // ok<bar />; // error
上記の例では、<foo />
は正常に動作しますが、<bar />
は JSX.IntrinsicElements
で指定されていないためエラーになります。
注:
JSX.IntrinsicElements
に、以下のようにキャッチオール文字列インデクサーを指定することもできます。
ts
declare namespace JSX {interface IntrinsicElements {[elemName: string]: any;}}
値ベースの要素
値ベースの要素は、スコープ内にある識別子によって単純に検索されます。
ts
import MyComponent from "./myComponent";<MyComponent />; // ok<SomeOtherComponent />; // error
値ベースの要素を定義するには、2 つの方法があります。
- 関数コンポーネント (FC)
- クラスコンポーネント
JSX 式では、これら 2 種類の値ベースの要素を互いに区別できないため、まず TS はオーバーロード解決を使用して式を関数コンポーネントとして解決しようとします。プロセスが成功すると、TS は式の解決をその宣言で完了します。値が関数コンポーネントとして解決できない場合、TS はクラスコンポーネントとして解決しようとします。それでも失敗すると、TS はエラーを報告します。
関数コンポーネント
名前が示すように、コンポーネントは JavaScript 関数として定義され、最初の引数は props
オブジェクトです。TS は、その戻り値の型が JSX.Element
に代入可能でなければならないことを強制します。
ts
interface FooProp {name: string;X: number;Y: number;}declare function AnotherComponent(prop: { name: string });function ComponentFoo(prop: FooProp) {return <AnotherComponent name={prop.name} />;}const Button = (prop: { value: string }, context: { color: string }) => (<button />);
関数コンポーネントは単なる JavaScript 関数であるため、ここでも関数のオーバーロードを使用できます。
tsTry
interfaceClickableProps {children :JSX .Element [] |JSX .Element ;}interfaceHomeProps extendsClickableProps {home :JSX .Element ;}interfaceSideProps extendsClickableProps {side :JSX .Element | string;}functionMainButton (prop :HomeProps ):JSX .Element ;functionMainButton (prop :SideProps ):JSX .Element ;functionMainButton (prop :ClickableProps ):JSX .Element {// ...}
注: 関数コンポーネントは、以前はステートレス関数コンポーネント (SFC) と呼ばれていました。最近のバージョンの React では、関数コンポーネントはもはやステートレスと見なすことができないため、型
SFC
とそのエイリアスStatelessComponent
は非推奨になりました。
クラスコンポーネント
クラスコンポーネントの型を定義することは可能です。ただし、そのためには、*要素クラスタイプ*と*要素インスタンスタイプ*という 2 つの新しい用語を理解するのが最善です。
<Expr />
が与えられた場合、*要素クラスタイプ*は Expr
の型です。したがって、上記の例では、MyComponent
が ES6 クラスである場合、クラスタイプはそのクラスのコンストラクターと静的メンバになります。 MyComponent
がファクトリ関数である場合、クラスタイプはその関数になります。
クラスタイプが確立されると、インスタンスタイプは、クラスタイプのコンストラクトまたはコールシグネチャ(どちらか存在する方)の戻り値の型の和集合によって決定されます。したがって、ここでも、ES6 クラスの場合、インスタンスタイプはそのクラスのインスタンスの型になり、ファクトリ関数の場合は、関数から返される値の型になります。
ts
class MyComponent {render() {}}// use a construct signatureconst myComponent = new MyComponent();// element class type => MyComponent// element instance type => { render: () => void }function MyFactoryFunction() {return {render: () => {},};}// use a call signatureconst myComponent = MyFactoryFunction();// element class type => MyFactoryFunction// element instance type => { render: () => void }
要素インスタンスタイプは、JSX.ElementClass
に代入可能でなければならないため、興味深いものです。そうでない場合はエラーになります。デフォルトでは JSX.ElementClass
は {}
ですが、JSX の使用を適切なインターフェースに準拠する型に制限するために拡張できます。
ts
declare namespace JSX {interface ElementClass {render: any;}}class MyComponent {render() {}}function MyFactoryFunction() {return { render: () => {} };}<MyComponent />; // ok<MyFactoryFunction />; // okclass NotAValidComponent {}function NotAValidFactoryFunction() {return {};}<NotAValidComponent />; // error<NotAValidFactoryFunction />; // error
属性の型チェック
属性の型チェックの最初のステップは、*要素属性タイプ*を決定することです。これは、組み込み要素と値ベースの要素でわずかに異なります。
組み込み要素の場合、これは JSX.IntrinsicElements
のプロパティの型です。
ts
declare namespace JSX {interface IntrinsicElements {foo: { bar?: boolean };}}// element attributes type for 'foo' is '{bar?: boolean}'<foo bar />;
値ベースの要素の場合、少し複雑です。これは、以前に決定された*要素インスタンスタイプ*のプロパティの型によって決定されます。どのプロパティを使用するかは、JSX.ElementAttributesProperty
によって決定されます。これは、単一のプロパティで宣言する必要があります。そのプロパティの名前が使用されます。TypeScript 2.8 以降、JSX.ElementAttributesProperty
が提供されない場合、クラス要素のコンストラクターまたは関数コンポーネントの呼び出しの最初のパラメーターの型が代わりに使用されます。
ts
declare namespace JSX {interface ElementAttributesProperty {props; // specify the property name to use}}class MyComponent {// specify the property on the element instance typeprops: {foo?: string;};}// element attributes type for 'MyComponent' is '{foo?: string}'<MyComponent foo="bar" />;
要素属性タイプは、JSX の属性の型チェックに使用されます。オプションのプロパティと必須のプロパティがサポートされています。
ts
declare namespace JSX {interface IntrinsicElements {foo: { requiredProp: string; optionalProp?: number };}}<foo requiredProp="bar" />; // ok<foo requiredProp="bar" optionalProp={0} />; // ok<foo />; // error, requiredProp is missing<foo requiredProp={0} />; // error, requiredProp should be a string<foo requiredProp="bar" unknownProp />; // error, unknownProp does not exist<foo requiredProp="bar" some-unknown-prop />; // ok, because 'some-unknown-prop' is not a valid identifier
注: 属性名が有効な JS 識別子でない場合(
data-*
属性など)、要素属性タイプで見つからない場合でもエラーとは見なされません。
さらに、JSX.IntrinsicAttributes
インターフェースを使用して、JSX フレームワークで使用される追加のプロパティを指定できます。これらのプロパティは、一般にコンポーネントのプロパティまたは引数では使用されません。たとえば、React の key
などです。さらに特化して、ジェネリック JSX.IntrinsicClassAttributes<T>
型を使用して、クラスコンポーネント(関数コンポーネントではない)のみに対して同じ種類の追加属性を指定することもできます。この型では、ジェネリックパラメーターはクラスインスタンスタイプに対応します。React では、これは Ref<T>
型の ref
属性を許可するために使用されます。一般的に、JSX フレームワークのユーザーがすべてのタグに何らかの属性を提供する必要があると意図していない限り、これらのインターフェースのすべてのプロパティはオプションにする必要があります。
スプレッド演算子も機能します。
ts
const props = { requiredProp: "bar" };<foo {...props} />; // okconst badProps = {};<foo {...badProps} />; // error
子要素の型チェック
TypeScript 2.3 では、TS は*子要素*の型チェックを導入しました。*子要素*は、*要素属性タイプ*の特別なプロパティであり、子 *JSXExpression* が属性に挿入されると見なされます。TS が JSX.ElementAttributesProperty
を使用して*props*の名前を決定する方法と同様に、TS は JSX.ElementChildrenAttribute
を使用して、それらの props 内の*子要素*の名前を決定します。 JSX.ElementChildrenAttribute
は、単一のプロパティで宣言する必要があります。
ts
declare namespace JSX {interface ElementChildrenAttribute {children: {}; // specify children name to use}}
ts
<div><h1>Hello</h1></div>;<div><h1>Hello</h1>World</div>;const CustomComp = (props) => <div>{props.children}</div><CustomComp><div>Hello World</div>{"This is just a JS expression..." + 1000}</CustomComp>
他の属性と同様に、*子要素*の型を指定できます。たとえば、React タイピングを使用している場合、これによりデフォルトの型がオーバーライドされます。
ts
interface PropsType {children: JSX.Elementname: string}class Component extends React.Component<PropsType, {}> {render() {return (<h2>{this.props.children}</h2>)}}// OK<Component name="foo"><h1>Hello World</h1></Component>// Error: children is of type JSX.Element not array of JSX.Element<Component name="bar"><h1>Hello World</h1><h2>Hello World</h2></Component>// Error: children is of type JSX.Element not array of JSX.Element or string.<Component name="baz"><h1>Hello</h1>World</Component>
JSX の結果の型
デフォルトでは、JSX 式の結果は any
型に設定されます。 JSX.Element
インターフェースを指定することで、型をカスタマイズできます。ただし、このインターフェースから JSX の要素、属性、または子に関する型情報を取得することはできません。これはブラックボックスです。
式の埋め込み
JSX では、式を中括弧({ }
)で囲むことにより、タグの間に式を埋め込むことができます。
ts
const a = (<div>{["foo", "bar"].map((i) => (<span>{i / 2}</span>))}</div>);
上記のコードは、文字列を数値で割ることができないため、エラーになります。 preserve
オプションを使用する場合の出力は次のようになります。
ts
const a = (<div>{["foo", "bar"].map(function (i) {return <span>{i / 2}</span>;})}</div>);
React との統合
JSX を React で使用するには、React タイピングを使用する必要があります。これらのタイピングは、React で使用するために JSX
名前空間を適切に定義します。
ts
/// <reference path="react.d.ts" />interface Props {foo: string;}class MyComponent extends React.Component<Props, {}> {render() {return <span>{this.props.foo}</span>;}}<MyComponent foo="bar" />; // ok<MyComponent foo={0} />; // error
JSX の設定
JSX をカスタマイズするために使用できるコンパイラフラグは複数あり、コンパイラフラグとしても、ファイルごとのインラインプラグマとしても機能します。詳細については、tsconfig リファレンスページを参照してください。