JSXとはReact.jsで採用されているJavaScriptの構文中にHTMLタグに類似した形でコンポーネントの状態を記述する形式です。様々な要素がHTMLのように記述できるので、プログラミングが容易になります。実際に動作させるにはJavaScriptへのコンパイルが必要です。
JSXを拡張してTypeScriptに対応させたものがTSXとなります。以降、基本的な解説はTSXを前提に行います。
const Page = () => (
<>
<div>あいうえお</div>
<div>
{new Array(10).fill("").map((_, index) => (
<span key={index}>あ</span>
))}
</div>
</>
);
export default Page;
詳しい内容は後述しますが、HTMLタグのように簡単に書ける反面、ループ処理のように見た目が分かりにくくなる部分もあります。
以下、何もしない関数コンポーネントです。なお、React.jsにはクラスコンポーネントという書き方もありますが、現在は使用されなくなってきているため、本書では解説しません。
function関数とアロー関数式の書き方を載せていますが、現在はアロー関数式を使うのが一般的です。また、functionとアロー関数式は仕組みが一部異なってますが、その原因となっているthisを関数コンポーネントで用いることはないので、違いを意識するケースはほぼありません。
また、一般のタグと作成したコンポーネントを区別するため、頭文字は大文字必須です。
const Component01 = () => <></>
const Component02 = () => null
const Component03 = () => { return <></> }
const Component04 = () => { return null }
const Component05 = function () { return <></> }
const Component06 = function () { return null }
関数コンポーネントは仮想DOMを構築するためのReactNodeを返します。その際、何もする必要が無ければ空のNodeを<></>
もしくはnull
で返します。
const Component01 = () =>
<div>
React.jsプログラミング
</div>
const Component02 = () =>
<>
<div>
React.jsプログラミング1
</div>
<div>
React.jsプログラミング2
</div>
</>
TSXによるReactNodeの記述はHTMLの記述ルールに近いものになっています。トップレベルに配置でいるノードは一つだけで、複数並べる必要がある場合は、何らかのノードの下に置くか、<></>
を使ってグループ化します。
const Component01 = () =>
<div className="a" style={{ width: "100px", height: "200px" }}>
React.jsプログラミング
</div>
const style = { width: "100px", height: "200px" }
const Component02 = () =>
<div className={"a"} style={style}>
React.jsプログラミング
</div>
TSXでタグのパラメータを記述する際、文字列に関してはそのまま書くことができます。それ以外の肩を持つデータは{}
で囲む必要があります。またHTMLならクラス名をclass
とするところですが、TSXではJavaScriptの予約語と被らないようにclassNameになっています。その他、styleの記述ではオブジェクトを渡す形式になっているなど、パラメータの種類によって書き方が一般的なHTMLとは異なっていることに注意が必要です。
import { FC, ReactNode } from "react";
interface Props {
children?: ReactNode;
}
const Component01 = (props: Props) => <div>入力された内容は「{props.children}」</div>;
const Component02 = ({ children }: Props) => <div>入力された内容は「{children}」</div>;
const Component03: FC = (props) => <div>入力された内容は「{props.children}」</div>;
const Component04: FC = ({ children }) => <div>入力された内容は「{children}」</div>;
const Page = () => (
<>
<Component01>React.jsプログラミング1</Component01>
<Component02>React.jsプログラミング2</Component02>
<Component03>React.jsプログラミング3</Component03>
<Component04>React.jsプログラミング4</Component04>
</>
);
export default Page;
4つのコンポーネントは記述に違いがあるものの同じ内容です。Reactでは子ノードとして送られたデータはchildrenというプロパティになります。
FC型として作成すると、childrenが定義済みとなります。Reactv18ではFCからchildrenの定義が削除されるという話もあるので、使用するかどうかは各々で判断してください。
マークアップ中に変数やJavaScriptの機能を利用する場合は{}
で囲みます。今回は{children}
で送られてきた子ノードを表示しています。
import { FC, ReactNode, ReactElement } from "react";
interface Props {
num: number;
children?: ReactNode;
}
const Component01: FC<Props> = ({ num, children }) => (
<>
<div>入力された数値は「{num}」</div>
<div>内容は「{children}」</div>
</>
);
const Page = () => (
<>
<Component01 num={1}>React.jsプログラミング</Component01>
</>
);
export default Page;
childrenの他に数値型のパラメータを渡しています。
import { FC, ReactNode, ReactElement } from "react";
interface Props {
num: number;
node: ReactNode;
}
const Component01: FC<Props> = ({ num, node }) => (
<>
<div>入力された数値は「{num}」</div>
<div>内容は「{node}」</div>
</>
);
const Page = () => <Component01 num={1} node={<h1>React.jsプログラミング</h1>} />;
export default Page;
コンポーネントの中にコンポーネントを記述する際、childrenの代わりにパラメータでReactNodeを渡すことも出来ます。
import { VFC } from "react";
interface Props {
num: number;
}
const Component01: VFC<Props> = ({ num }) => (
<div>
{num === 0 && "あいうえお"}
{num === 1 && "かきくけこ"}
{num === 2 && "さしすせそ"}
{num === 3 && "たちつてと"}
</div>
);
const Page = () => (
<>
<Component01 num={1} />
<Component01 num={3} />
</>
);
export default Page;
マークアップ内で条件を指定することも出来ます。ただし気をつけなければならないのは{num && "なにぬねの"}
です。numが0だった場合、0
が表示されます。JavaScriptでは&&で真にならなかった場合、最初の値が返るという仕様があるのですがnull
,undefined
,false
なら何も表示されませんが、0は数値なので出力されることになります。
import { VFC } from "react";
interface Props {
num: number;
}
const Component01: VFC<Props> = ({ num }) => (
<div>
{Array(num).fill("").map((_, index) => (
<div key={index}>{index}:あいうえお</div>
))}
</div>
);
const Page = () => (
<>
<Component01 num={5} />
</>
);
export default Page;
マークアップ内ではfor分に相当する機能がありません。そのためループのような処理を行うためには配列が必要になります。ここではArrayで配列を作成しています。fillで塗りつぶしているのは、領域だけ作成した状態だと配列サイズが0だと認識されてループ処理が出来ないためです。
また<div key={index}>
のように、ループ内のトップノードにはkeyが必要です。これは仮想DOMから実際のDOMに変換する際に、どのノードと結びついているかを判断するためです。また、今回はkeyに相当するものがindexしかないのでそのまま利用していますが、本来ならユニークなIDが設定されていることが前提となります。これを怠ると、送ったデータと表示されている内容が相違することがあります。
Reactでマークアップを行う際に利用できるデータの種類は以下のような内容になります
import { ElementType } from "react";
const Page = () => {
const Test01 = "<h1>テスト01</h1>";
const Test02 = 100;
const Test03 = true;
const Test04 = false;
const Test05 = null;
const Test06 = undefined;
const Test07 = ["T", "E", "S", "T", "0", "7"];
const Test08 = () => <>テスト08</>;
const Test09:ElementType = "h1";
const Test10 = "<h2>Test10</h2>";
return (
<>
<div>Test01:{Test01}</div>
<div>Test02:{Test02}</div>
<div>Test03:{Test03}</div>
<div>Test04:{Test04}</div>
<div>Test05:{Test05}</div>
<div>Test06:{Test06}</div>
<div>Test07:{Test07}</div>
<div>
<Test08 />
</div>
<Test09>テスト09</Test09>
<div dangerouslySetInnerHTML={ { __html: Test10 } } />
</>
);
};
export default Page;
const Page = () => (
<form
onSubmit={(e) => {
e.preventDefault(); //ページ切り替えのキャンセル
const form = e.target as HTMLFormElement;
console.log(`フォーム実行内容:${form["a"].value}/${form["b"].checked}`);
}}
>
<input name="a" onChange={(e) => console.log(`テキスト:${e.target.value}`)} />
<input name="b" type="checkbox" onChange={(e) => console.log(`チェック:${e.target.checked}`)} />
<button>実行</button>
</form>
);
export default Page;
イベントのデータをconsole.logで表示しています。一般的なJavaScriptのイベント処理と同等ですが、thisを使ったノードの取得が出来ないので気をつけてください。
import { ChangeEventHandler, FormEventHandler } from "react";
const Page = () => {
const handleSubmit: FormEventHandler<HTMLFormElement> = (e) => {
e.preventDefault(); //ページ切り替えのキャンセル
const form = e.currentTarget;
console.log(`フォーム実行内容:${form["a"].value}/${form["b"].checked}`);
};
const handleTextChange: ChangeEventHandler<HTMLInputElement> = (e) =>
console.log(`テキスト:${e.target.value}`);
const handleCheckChange: ChangeEventHandler<HTMLInputElement> = (e) =>
console.log(`チェック:${e.currentTarget.checked}`);
return (
<form onSubmit={handleSubmit}>
<input name="a" onChange={handleTextChange} />
<input name="b" type="checkbox" onChange={handleCheckChange} />
<button>実行</button>
</form>
);
};
export default Page;
イベント処理をマークアップに入る前に記述する書き方です。TypeScriptを使う上で問題になるのが、事前に書く場合は、関数の型を自分で設定する必要があります。必要な型を丸暗記する必要はありません。マウスを載せれば教えてくれます。