JSX

イントロダクション

JSXは埋め込み可能なXML風の文法です。 これは、適切なJavaScriptに変換されますが、その変換のセマンティクスは独自実装になっています。

JSXはReactフレームワークで人気を博しましたが、別のアプリケーションでも同様に見られるようになってきました。 TypeScriptは埋め込み、型チェック、JSXの直接的なJavaScriptへのコンパイルをサポートします。

基本的な使い方

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

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

TypeScriptは、preservereactの2つのJSXモードをサポートします。 これらのモードは出力時の段階だけに影響するもので、型チェックには影響しません。

preserveモードでは、別の変換ステップ(例えばBabel)で更に変換される出力の一部としてJSXが保持されます。 加えて、出力の拡張子は.jsxになります。

reactモードはReact.createElementを出力し、 使用する前にJSX変換を行う必要はなく、出力には.jsファイル拡張子が付きます。

モード 入力 出力 出力ファイル拡張子
preserve <div /> <div /> .jsx
react <div /> React.createElement("div") .js

このモードは、--jsxコマンドラインのフラグ、 またはtsconfig.jsonファイルの対応するオプションを使用して指定できます。

注意: 識別子Reactはハードコードされているため、Reactを大文字のRで使用する必要があります。

as演算子

型注釈(type assertion)をどのように書くのかを思い出してみましょう。

var foo = <foo>bar;

ここでは、変数barの型がfooであることを宣言しています。 TypeScriptでは、この型注釈(type assertion)に角括弧も使用されるため、JSXの構文によって構文解析の難解さが増します。 その結果、TypeScriptは.tsx内で角括弧を使用した型注釈(type assertion)を禁止します。

var foo = bar as foo;

as演算子は、.tsファイルと.tsxファイルの両方で使用でき、 動作は他の型注釈(type assertion)の形式と同じです。

型のチェック

JSXでの型チェックを理解するために、まず本質的(intrinsic)要素と値ベース(value-based)要素の違いを理解する必要があります。 JSXの式<expr />が指定されると、exprはその環境での本質的な(intrinsic)何か(例: DOM環境であればdivまたはspan)、 あなたが作成したカスタムコンポーネントのどちらかを参照します。 これは2つの意味で重要です。

  1. Reactでは組み込み要素は文字列(React.createElement("div"))として出力され、 一方であなたが作成したコンポーネントは文字列ではありません。(React.createElement(MyComponent))
  2. JSX要素に渡される属性の型は、異なる方法で検索する必要があります。 組み込み要素の属性は同じく本質的なものなので予め分かるはずですが、 コンポーネントはおそらく独自の属性を指定する必要があるでしょう。

TypeScriptは、Reactがこれらを区別するのと同じルールを適用します。 組み込み要素は常に小文字で始まり、値ベースの要素は常に大文字で始まります。

組み込み要素(Intrinsic elements)

組み込み要素はJSX.IntrinsicElementsという特別なインタフェースで参照されます。 デフォルトでは、このインターフェイスが指定されていない場合は組み込み要素は型チェックされません。

ただし、インターフェースが指定された場合は、 組み込み要素の名前がJSX.IntrinsicElementsインタフェースのプロパティとして参照されます。 下記はその例になります。

declare namespace JSX {
    interface IntrinsicElements {
        foo: any
    }
}

<foo />; // ok
<bar />; // error

上記の例では、<foo />は正常に動作しますが、 <bar />JSX.IntrinsicElementsが指定されなかったため、エラーになります。

注意: JSX.IntrinsicElementsで、下記のようにして全てを補足する文字列インデックスを指定することも可能です。

declare namespace JSX {
   interface IntrinsicElements {
       [elemName: string]: any;
   }
}

値ベース要素

値ベースの要素は、スコープ内にある識別子によって参照されます。

import MyComponent from "./myComponent";

<MyComponent />; // ok
<SomeOtherComponent />; // error

値ベース要素の型は、制限することが可能です。 しかし、このためには要素クラス型と要素インスタンス型の2つの新しい条件を指定する必要があります。

<Expr />が指定された場合、要素クラスの型はExpr型になります。 上記の例では、MyComponentがES6のクラスの場合、そのクラスの型はクラスになります。 もし、MyComponentがファクトリー関数であった場合、そのクラス型は関数になります。

クラスの型が確立されると、インスタンス型はクラス型の呼び出しシグネチャの戻り値の型とコンストラクタのシグネチャの和集合によって決定されます。 したがって、ES6クラスの場合はインスタンスの型はそのクラスのインスタンスの型になり、 ファクトリ関数の場合は関数から返される値の型になります。

class MyComponent {
  render() {}
}

// コンストラクタ・シグネチャを使用
var myComponent = new MyComponent();

// 要素のクラス型 => MyComponent
// 要素のインスタンス型 => { render: () => void }



function MyFactoryFunction() {
  return {
    render: () => {
    }
  }
}

// 呼び出しシグネチャを使用
var myComponent = MyFactoryFunction();

// 要素のクラス型 => FactoryFunction
// 要素のインスタンス型 => { render: () => void }

要素インスタンスの型は、JSX.ElementClassに割り当て可能でなければならないのが興味深い点です。 そうしなければエラーになります。

デフォルトではJSX.ElementClass{}ですが、 JSXの使用を適切なインタフェースに準拠させるために、型を限定させる拡張が可能になっています。

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のプロパティの型になります。

declare namespace JSX {
  interface IntrinsicElements {
    foo: { bar?: boolean }
  }
}

// 'foo'の場合、要素の属性の型は'{bar?: boolean}'になります。
<foo bar />;

値ベース要素の場合は、少し複雑です。 これは、事前に決定された要素インスタンス型のプロパティの型によって決定されます。 使用するプロパティは、JSX.ElementAttributesPropertyによって決まります。 これは単一のプロパティで宣言する必要があり、 そのプロパティの名前が使用されます。

declare namespace JSX {
  interface ElementAttributesProperty {
    props; // 使用するプロパティ名を指定
  }
}

class MyComponent {
  // 要素インスタンス型のプロパティを指定
  props: {
    foo?: string;
  }
}

// 'MyComponent'の場合、要素の属性は'{foo?: string}'
<MyComponent foo="bar" />

要素の属性の型が、JSXの属性の型チェックに使用されます。 任意のプロパティと必須のプロパティがサポートされます。

declare namespace JSX {
  interface IntrinsicElements {
    foo: { requiredProp: string; optionalProp?: number }
  }
}

<foo requiredProp="bar" />; // ok
<foo requiredProp="bar" optionalProp={0} />; // ok
<foo />; // エラー、requiredPropが存在しない
<foo requiredProp={0} />; // エラー、requiredPropは文字列でなければいけない
<foo requiredProp="bar" unknownProp />; // エラー、unknownPropは存在してはいけない
<foo requiredProp="bar" some-unknown-prop />; // ok, 'some-unknown-prop'は適切な識別子ではないため

注意: 属性名が適切なJS識別子ではない(data-*属性のような)場合、 要素の属性の型として見つからなくても、エラーとみなされません。

スプレッド演算子も動作します。

var props = { requiredProp: "bar" };
<foo {...props} />; // ok

var badProps = {};
<foo {...badProps} />; // error

JSXの結果の型

デフォルトでは、JSX式の結果はany型として扱われます。 これは、JSX.Elementインターフェースを指定することでカスタマイズすることが可能です。 ただし、このインタフェースからJSXの要素、属性、または子に関する型情報を取得することはできません。 ブラックボックスになります。

式の埋め込み

JSXでは、式を中括弧({ })で囲んで、タグの間に式を埋め込むことが可能になっています。

var a = <div>
  {["foo", "bar"].map(i => <span>{i / 2}</span>)}
</div>

上記の式は文字列を数値で割り算できないため、エラーになります。 preserveオプションを使用すると、出力は次のようになります。

var a = <div>
  {["foo", "bar"].map(function (i) { return <span>{i / 2}</span>; })}
</div>

Reactの統合

ReactでJSXを使用するには、React typingsを使用する必要があります。 これらの型指定には、Reactで使用するためのJSX名前空間が適切に定義されています。

/// <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

 Back to top

© https://github.com/Microsoft/TypeScript-Handbook

このページは、ページトップのリンク先のTypeScript-Handbook内のページを翻訳した内容を基に構成されています。 下記の項目を確認し、必要に応じて公式のドキュメントをご確認ください。 もし、誤訳などの間違いを見つけましたら、 @tomofまで教えていただければ幸いです。

  • ドキュメントの情報が古い可能性があります。
  • "訳注:"などの断わりを入れた上で、日本人向けの情報やより分かり易くするための追記を行っている事があります。