JSX

JSX は、埋め込み可能なXML風の構文です。有効なJavaScriptに変換されることを意図していますが、その変換のセマンティクスは実装固有です。JSXはReact フレームワークで人気が高まりましたが、それ以降、他の実装も登場しています。TypeScriptは、JSXの埋め込み、型チェック、およびJavaScriptへの直接コンパイルをサポートしています。

基本的な使い方

JSXを使用するには、2つのことを行う必要があります。

  1. ファイルに.tsx拡張子を付ける
  2. jsx オプションを有効にする

TypeScriptには、preservereactreact-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;

これは、変数barfoo型を持つことをアサートします。TypeScriptは型アサーションにも山かっこを使用するため、JSXの構文と組み合わせると、特定の解析が困難になります。そのため、TypeScriptは.tsxファイルでの山かっこ型アサーションを許可しません。

上記の構文は.tsxファイルでは使用できないため、代替の型アサーション演算子であるasを使用する必要があります。この例は、as演算子を使用して簡単に書き直すことができます。

ts
const foo = bar as foo;

as演算子は、.tsファイルと.tsxファイルの両方で使用でき、山かっこ型アサーションスタイルと同じ動作をします。

型チェック

JSXでの型チェックを理解するには、まず組み込み要素と値ベースの要素の違いを理解する必要があります。JSX式<expr />が与えられた場合、exprは環境に組み込まれているもの(例:DOM環境でのdivまたはspan)または作成したカスタムコンポーネントのいずれかを指します。これは2つの理由で重要です。

  1. React では、組み込み要素は文字列として出力されます(React.createElement("div"))。一方、自分で作成したコンポーネントは文字列として出力されません(React.createElement(MyComponent))。
  2. 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 つの方法があります。

  1. 関数コンポーネント (FC)
  2. クラスコンポーネント

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 関数であるため、ここでも関数のオーバーロードを使用できます。

ts
interface ClickableProps {
children: JSX.Element[] | JSX.Element;
}
 
interface HomeProps extends ClickableProps {
home: JSX.Element;
}
 
interface SideProps extends ClickableProps {
side: JSX.Element | string;
}
 
function MainButton(prop: HomeProps): JSX.Element;
function MainButton(prop: SideProps): JSX.Element;
function MainButton(prop: ClickableProps): JSX.Element {
// ...
}
Try

注: 関数コンポーネントは、以前はステートレス関数コンポーネント (SFC) と呼ばれていました。最近のバージョンの React では、関数コンポーネントはもはやステートレスと見なすことができないため、型 SFC とそのエイリアス StatelessComponent は非推奨になりました。

クラスコンポーネント

クラスコンポーネントの型を定義することは可能です。ただし、そのためには、*要素クラスタイプ*と*要素インスタンスタイプ*という 2 つの新しい用語を理解するのが最善です。

<Expr /> が与えられた場合、*要素クラスタイプ*は Expr の型です。したがって、上記の例では、MyComponent が ES6 クラスである場合、クラスタイプはそのクラスのコンストラクターと静的メンバになります。 MyComponent がファクトリ関数である場合、クラスタイプはその関数になります。

クラスタイプが確立されると、インスタンスタイプは、クラスタイプのコンストラクトまたはコールシグネチャ(どちらか存在する方)の戻り値の型の和集合によって決定されます。したがって、ここでも、ES6 クラスの場合、インスタンスタイプはそのクラスのインスタンスの型になり、ファクトリ関数の場合は、関数から返される値の型になります。

ts
class MyComponent {
render() {}
}
// use a construct signature
const myComponent = new MyComponent();
// element class type => MyComponent
// element instance type => { render: () => void }
function MyFactoryFunction() {
return {
render: () => {},
};
}
// use a call signature
const 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 />; // ok
class 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 type
props: {
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} />; // ok
const 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.Element
name: 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 リファレンスページを参照してください。

TypeScript のドキュメントはオープンソースプロジェクトです。プルリクエストを送信して、これらのページの改善にご協力ください

このページへの貢献者
MHMohamed Hegazy (55)
OTOrta Therox (20)
RCRyan Cavanaugh (6)
DZDavid Zulaica (3)
KTKanchalai Tanglertsampan (3)
32+

最終更新日: 2024年3月21日