MochiuWiki : SUSE, EC, PCB
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
Reactの基礎 - TSXの基本構文のソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
Reactの基礎 - TSXの基本構文
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == TSX (TypeScript XML) は、ReactアプリケーションをTypeScriptで記述するためのファイル形式 (<code>.tsx</code>) である。<br> JavaScriptの構文拡張であるJSXに、TypeScriptの静的型付けを組み合わせることで、UIコンポーネントの構造を宣言的に記述しながら、コンパイル時に型の整合性を検証できる。<br> <br> TSXを使用することにより、以下に示すようなメリットがある。<br> * Propsに不正な値を渡した場合にコンパイルエラーとして検出できる。 * イベントハンドラの引数型が自動推論され、エディタ上で入力補完が効くようになる。 * コンポーネントの戻り値やRefの型を明示することで、意図しない使い方を防止できる。 <br> React 18以降では <code>React.FC</code> からchildrenの暗黙的な型定義が削除され、TypeScript 5.1ではコンポーネントの戻り値型の制約が緩和される等、<br> TSXの記述スタイルに関する標準が整理されてきている。<br> <br> <center> {| class="wikitable" |+ TSXの基本構文の概要 ! カテゴリ !! 主な構文・型 !! 概要 |- | Propsの型定義 || <code>interface</code> / <code>type</code> || コンポーネントが受け取るPropsの型を定義する |- | コンポーネント宣言 || <code>React.FC</code> / 関数宣言 || 型付きコンポーネントの宣言スタイルを使い分ける |- | 状態管理の型付け || <code>useState</code> / <code>useReducer</code> || 状態とアクションに型を付与して安全に管理する |- | イベントハンドラの型 || <code>React.MouseEvent</code> / <code>React.ChangeEvent</code> 等 || クリックやフォーム入力等のイベントに型を指定する |- | Refの型付け || <code>useRef</code> || DOM要素やミュータブルな値に型を付与して参照する |- | childrenの型定義 || <code>React.ReactNode</code> / <code>PropsWithChildren</code> || 子要素を受け取るコンポーネントの型を定義する |- | ジェネリックコンポーネント || 型パラメータ (<code><T></code>) || 型パラメータにより汎用的なコンポーネントを実装する |- | 型アサーション || <code>as</code> 構文 || TSXではアングルブラケット構文が使用できないため、<code>as</code> 構文で型を明示する |} </center> <br><br> == TSXとは == ==== JSXからTSXへ ==== JSXはJavaScriptの構文拡張であり、UI構造をHTMLに近い形で記述できる。<br> TSXはこのJSXをTypeScriptで使用するためのファイル形式であり、拡張子は <code>.tsx</code> となる。<br> <br> <code>.tsx</code> ファイルでは、TypeScriptの型チェックがJSX構文にも適用されるため、以下に示すような恩恵が得られる。<br> * Propsに渡す値の型チェック * イベントハンドラの引数型チェック * コンポーネントの戻り値の型検証 * エディタによる補完・リファクタリング支援 <br> ==== tsconfig.jsonの設定 ==== TSXを使用するには、<code>tsconfig.json</code> の <code>jsx</code> オプションを適切に設定する必要がある。<br> React 17以降に導入された新しいJSXトランスフォームを利用する場合は、<code>"react-jsx"</code> を指定する。<br> <br> <syntaxhighlight lang="json"> { "compilerOptions": { "target": "ES2020", "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "moduleResolution": "bundler", "jsx": "react-jsx", "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, "include": ["src"] } </syntaxhighlight> <br> <code>"jsx": "react-jsx"</code> を設定すると、各ファイルで <code>import React from 'react'</code> を記述する必要がなくなる。<br> <code>"jsx": "react"</code> (旧来の設定) では、全てのTSXファイルにReactのインポートが必要である。<br> <br><br> == 型付きコンポーネントの基本 == ==== Propsの型定義 ==== コンポーネントのPropsは <code>interface</code> または <code>type</code> を使用して定義する。<br> コミュニティの標準では、<code>interface</code> による定義が広く採用されている。<br> <br> 基本的なPropsの型定義例を以下に示す。<br> <br> <syntaxhighlight lang="typescript"> interface ButtonProps { label: string; onClick: () => void; disabled?: boolean; variant?: "primary" | "secondary" | "danger"; } function Button({ label, onClick, disabled = false, variant = "primary" }: ButtonProps) { return ( <button onClick={onClick} disabled={disabled} className={`btn btn-${variant}`} > {label} </button> ); } </syntaxhighlight> <br> <u><code>?</code> を付けたプロパティはオプショナルとなり、省略可能になる。</u><br> <u>デフォルト値は関数引数の分割代入で指定する。</u><br> <br> ==== React.FC vs 関数宣言 ==== コンポーネントを定義する方法として、<code>React.FC</code> (または <code>React.FunctionComponent</code>) を使用する方法と、通常の関数宣言を使用する方法がある。<br> React 18以降、コミュニティの標準は明示的なProps型定義 (通常の関数宣言) に移行している。<br> <br> 2つのアプローチを比較する。<br> <br> <syntaxhighlight lang="typescript"> // React.FC を使う方法 (React 18以前でよく見られたスタイル) const Greeting: React.FC<{ name: string }> = ({ name }) => { return <p>Hello, {name}!</p>; }; // 推奨: 明示的なProps型定義を使う方法 interface GreetingProps { name: string; } function Greeting({ name }: GreetingProps) { return <p>Hello, {name}!</p>; } </syntaxhighlight> <br> <code>React.FC</code> を使わない理由は以下の通りである。<br> * React 18以降、<code>React.FC</code> は <code>children</code> を自動的に含まなくなったため、React 17以前との挙動が変わった。 * ジェネリックコンポーネントに対応しにくい。 * 通常の関数宣言の方がTypeScriptの型推論との親和性が高い。 <br> ==== 戻り値の型 ==== コンポーネントの戻り値には <code>JSX.Element</code> または <code>React.ReactNode</code> が使用される。<br> 通常は型推論に任せ、明示的な指定が必要な場合のみ記述する。<br> <br> <syntaxhighlight lang="typescript"> // JSX.Element: JSX要素のみを返す場合 function Title(): JSX.Element { return <h1>タイトル</h1>; } // React.ReactNode: nullや文字列も返す可能性がある場合 function MaybeContent({ show }: { show: boolean }): React.ReactNode { if (!show) return null; return <p>コンテンツ</p>; } // 型推論に任せる方法 (推奨) function AutoInferred({ text }: { text: string }) { return <span>{text}</span>; } </syntaxhighlight> <br><br> == 状態管理の型付け == ==== useStateの型 ==== <code>useState</code> は初期値から型を推論できる場合と、明示的に型を指定する必要がある場合がある。<br> <br> <syntaxhighlight lang="typescript"> import { useState } from "react"; interface User { id: number; name: string; email: string; } function UserForm() { // 初期値から型推論が可能なケース const [count, setCount] = useState(0); // number型 const [isLoading, setIsLoading] = useState(false); // boolean型 const [message, setMessage] = useState(""); // string型 // 明示的な型指定が必要なケース const [user, setUser] = useState<User | null>(null); // null初期値 const [users, setUsers] = useState<User[]>([]); // 空配列 const [selected, setSelected] = useState<number | undefined>(undefined); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>増やす</button> </div> ); } </syntaxhighlight> <br> nullや空配列を初期値として使用する場合は、TypeScriptが型を推論できないため、明示的なジェネリック指定が必要となる。<br> <br> ==== useReducerの型 ==== <code>useReducer</code> では、状態型 (State) とアクション型 (Action) を定義する。<br> <br> アクション型は判別共用体 (Discriminated Union) を使用して定義するのが推奨パターンである。<br> <br> <syntaxhighlight lang="typescript"> import { useReducer } from "react"; // 状態の型定義 interface CounterState { count: number; step: number; } // アクションの型定義 (判別共用体) type CounterAction = | { type: "increment" } | { type: "decrement" } | { type: "reset" } | { type: "setStep"; payload: number }; // reducerの実装 function counterReducer(state: CounterState, action: CounterAction): CounterState { switch (action.type) { case "increment": return { ...state, count: state.count + state.step }; case "decrement": return { ...state, count: state.count - state.step }; case "reset": return { ...state, count: 0 }; case "setStep": return { ...state, step: action.payload }; default: return state; } } function Counter() { const [state, dispatch] = useReducer(counterReducer, { count: 0, step: 1 }); return ( <div> <p>Count: {state.count} (Step: {state.step})</p> <button onClick={() => dispatch({ type: "increment" })}>+</button> <button onClick={() => dispatch({ type: "decrement" })}>-</button> <button onClick={() => dispatch({ type: "reset" })}>リセット</button> <button onClick={() => dispatch({ type: "setStep", payload: 5 })}>Step: 5</button> </div> ); } </syntaxhighlight> <br> 判別共用体を使用することにより、各アクションの <code>payload</code> に対する型チェックが正確に機能する。<br> 例えば、<code>setStep</code> アクションでは <code>payload</code> が必須となり、他のアクションでは不要であることを型として表現できる。<br> <br><br> == イベントハンドラの型 == ==== 一般的なイベント型 ==== Reactは、DOMイベントをラップした独自のSyntheticEvent型を提供している。<br> <br> 下表に、主なイベント型を示す。<br> <br> <center> {| class="wikitable" |+ Reactの主なイベント型 ! イベント型 !! 対象要素の例 !! 用途 |- | <code>React.ChangeEvent<HTMLInputElement></code> || input, textarea, select || 入力値の変更 |- | <code>React.MouseEvent<HTMLButtonElement></code> || button, div, span || マウスクリック・移動 |- | <code>React.FormEvent<HTMLFormElement></code> || form || フォーム送信 |- | <code>React.KeyboardEvent<HTMLInputElement></code> || input, textarea || キーボード入力 |- | <code>React.FocusEvent<HTMLInputElement></code> || input, select || フォーカスの取得・喪失 |- | <code>React.DragEvent<HTMLDivElement></code> || div, img || ドラッグ&ドロップ |- | <code>React.WheelEvent<HTMLDivElement></code> || div, canvas || マウスホイール |} </center> <br> ==== イベントハンドラ関数の型定義 ==== イベントハンドラは、JSX属性のインライン定義と、別途関数として定義する2つの方法がある。<br> <br> <syntaxhighlight lang="typescript"> import { useState } from "react"; function Form() { const [inputValue, setInputValue] = useState(""); const [isSubmitted, setIsSubmitted] = useState(false); // 関数として型を明示して定義 const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { setInputValue(event.target.value); }; const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); setIsSubmitted(true); }; const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => { if (event.key === "Enter") { console.log("Enterキーが押されました:", inputValue); } }; const handleButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => { console.log("クリック位置:", event.clientX, event.clientY); }; return ( <form onSubmit={handleSubmit}> <input type="text" value={inputValue} onChange={handleChange} onKeyDown={handleKeyDown} placeholder="テキストを入力" /> <button type="submit" onClick={handleButtonClick}> 送信 </button> {isSubmitted && <p>送信されました: {inputValue}</p>} </form> ); } </syntaxhighlight> <br> イベントオブジェクトを使用しない場合は、引数の型注釈を省略しても型推論が機能する。<br> しかし、イベントオブジェクトのプロパティ (例: <code>event.target.value</code>) にアクセスする場合は、明示的な型指定が必要となる。<br> <br><br> == Refの型付け == ==== useRefの型 ==== <code>useRef</code> には、DOM要素への参照と、ミュータブルな値の保持という2つの用途がある。<br> それぞれで型の指定方法が異なる。<br> <br> <syntaxhighlight lang="typescript"> import { useRef, useEffect } from "react"; function InputWithFocus() { // DOM要素への参照: 初期値はnull、型引数にHTML要素型を指定 const inputRef = useRef<HTMLInputElement>(null); const divRef = useRef<HTMLDivElement>(null); // ミュータブル値の保持: 初期値が null の場合 const timerIdRef = useRef<number | null>(null); const renderCountRef = useRef<number>(0); useEffect(() => { // DOM参照はnullチェックが必要 if (inputRef.current) { inputRef.current.focus(); } // ミュータブル値はnullチェック不要 (初期値が0の場合) renderCountRef.current += 1; }); const startTimer = () => { timerIdRef.current = window.setTimeout(() => { console.log("タイマー発火"); }, 1000); }; const stopTimer = () => { if (timerIdRef.current !== null) { clearTimeout(timerIdRef.current); timerIdRef.current = null; } }; return ( <div ref={divRef}> <input ref={inputRef} type="text" placeholder="フォーカスされます" /> <button onClick={startTimer}>タイマー開始</button> <button onClick={stopTimer}>タイマー停止</button> </div> ); } </syntaxhighlight> <br> <u>DOM要素への参照では <code>null</code> を初期値とし、使用時に <code>null</code> チェックを行う。</u><br> <u>ミュータブル値では、値の型に合わせた初期値を設定する。</u><br> <br><br> == childrenの型 == ==== React.ReactNode ==== <code>children</code> プロパティの型として最も汎用的なのが <code>React.ReactNode</code> である。<br> 文字列、数値、JSX要素、配列、null、undefined など、レンダリング可能なあらゆる値を受け付ける。<br> <br> <syntaxhighlight lang="typescript"> import { ReactNode } from "react"; interface CardProps { title: string; children: ReactNode; footer?: ReactNode; } function Card({ title, children, footer }: CardProps) { return ( <div className="card"> <div className="card-header"> <h2>{title}</h2> </div> <div className="card-body"> {children} </div> {footer && ( <div className="card-footer"> {footer} </div> )} </div> ); } // 使用例 function App() { return ( <Card title="ユーザー情報" footer={<button>編集</button>} > <p>名前: 山田 太郎</p> <p>メール: yamada@example.com</p> </Card> ); } </syntaxhighlight> <br> <u><code>React.ReactNode</code> はJSX要素だけでなく、文字列や数値も受け付けるため、柔軟なコンポーネント設計が可能となる。</u><br> <br> ==== PropsWithChildren ==== <code>React.PropsWithChildren</code> は、既存のProps型に <code>children?: ReactNode</code> を追加するユーティリティ型である。<br> <br> React 17以前に <code>React.FC</code> と組み合わせて使われていたパターンだが、現在は明示的に <code>children: ReactNode</code> を定義する方が推奨されている。<br> <br> <syntaxhighlight lang="typescript"> import { PropsWithChildren, ReactNode } from "react"; // 推奨: childrenを明示的に定義する方法 interface LayoutProps { title: string; children: ReactNode; } function Layout({ title, children }: LayoutProps) { return ( <div> <h1>{title}</h1> <main>{children}</main> </div> ); } // 非推奨: PropsWithChildrenを使う方法 (React 17以前のスタイル) interface OldLayoutProps { title: string; } function OldLayout({ title, children }: PropsWithChildren<OldLayoutProps>) { return ( <div> <h1>{title}</h1> <main>{children}</main> </div> ); } </syntaxhighlight> <br> <code>PropsWithChildren</code> を使用せずに明示的に定義するメリットは、以下の通りである。<br> * <code>children</code> が必須か任意かをコントロールできる。 * Props型を見るだけで <code>children</code> の存在が明確に分かる。 * ソースコードの意図が明示的になる。 <br><br> == ジェネリックコンポーネント == ==== 型パラメータを持つコンポーネント ==== コンポーネントに型パラメータを持たせることにより、汎用的な再利用可能なコンポーネントを定義できる。<br> <br> <code>function</code> 宣言とアロー関数では、ジェネリックの記述方法が異なる琴に注意が必要である。<br> <br> <syntaxhighlight lang="typescript"> import { ReactNode } from "react"; // function宣言によるジェネリックコンポーネント interface ListProps<T> { items: T[]; renderItem: (item: T, index: number) => ReactNode; keyExtractor: (item: T) => string; emptyMessage?: string; } function List<T>({ items, renderItem, keyExtractor, emptyMessage = "データがありません" }: ListProps<T>) { if (items.length === 0) { return <p>{emptyMessage}</p>; } return ( <ul> {items.map((item, index) => ( <li key={keyExtractor(item)}> {renderItem(item, index)} </li> ))} </ul> ); } // アロー関数によるジェネリックコンポーネント // .tsx ファイルでは <T> が JSX タグと誤認されるため、<T,> とカンマが必要 const SelectBox = <T extends { id: string; label: string },>( props: { options: T[]; value: string; onChange: (value: string) => void; } ) => { return ( <select value={props.value} onChange={(e) => props.onChange(e.target.value)}> {props.options.map((option) => ( <option key={option.id} value={option.id}> {option.label} </option> ))} </select> ); }; // 使用例 interface User { id: string; name: string; email: string; } function UserList() { const users: User[] = [ { id: "1", name: "山田 太郎", email: "yamada@example.com" }, { id: "2", name: "佐藤 花子", email: "sato@example.com" }, ]; return ( <List items={users} keyExtractor={(user) => user.id} renderItem={(user) => ( <span>{user.name} ({user.email})</span> )} /> ); } </syntaxhighlight> <br> <u>アロー関数でジェネリックを使う場合の <code><T,></code> というカンマは、TSXファイルにおいてJSXタグと区別するために必要な構文である。</u><br> <u><code>extends</code> 制約を使うと、型パラメータが特定のプロパティを持つことを保証できる。</u><br> <br><br> == 型アサーションとTSX == ==== as構文の使用 ==== TypeScriptでは、型アサーションに2種類の構文がある。<br> <br> <u>TSXファイルではアングルブラケット構文 (<code><Type>value</code>) がJSXタグと競合するため使用できない。</u><br> <u>代わりに <code>as</code> 構文を使用する。</u><br> <br> <syntaxhighlight lang="typescript"> // 通常の.tsファイルでは両方使用可能 // アングルブラケット構文 (TSXファイルでは使用不可) // const element = <HTMLInputElement>document.getElementById("input"); // as構文 (TSXファイルで使用する方法) const element = document.getElementById("input") as HTMLInputElement; // as構文の様々な使用例 function processData(data: unknown) { // unknown型から特定の型へのアサーション const user = data as { name: string; age: number }; console.log(user.name); } // イベントターゲットへのアサーション function handleChange(event: React.ChangeEvent<HTMLInputElement>) { const target = event.target as HTMLInputElement; console.log(target.value); } // constアサーション (リテラル型を保持) const directions = ["up", "down", "left", "right"] as const; type Direction = typeof directions[number]; // "up" | "down" | "left" | "right" interface DirectionButtonProps { direction: Direction; onClick: (direction: Direction) => void; } function DirectionButton({ direction, onClick }: DirectionButtonProps) { return ( <button onClick={() => onClick(direction)}> {direction} </button> ); } // satisfies演算子 (TypeScript 4.9以降): 型チェックしつつ型推論を保持 const config = { apiUrl: "https://api.example.com", timeout: 5000, retries: 3, } satisfies Record<string, string | number>; </syntaxhighlight> <br> 型アサーションは型システムを迂回する操作であるため、過度な使用は避けるべきである。<br> <u>型ガード (type guard) や 型推論で対応できる場合は、そちらを優先する。</u><br> <br><br> == 関連情報 == * [[Reactの基礎 - JSXの基本構文]] * [[Reactの基礎 - コンポーネント]] * [[Reactの基礎 - PropsとChildren]] * [[Reactの基礎 - 条件レンダリング]] * [[Reactの基礎 - リストレンダリング]] <br><br> {{#seo: |title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki |keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,電気回路,電子回路,基板,プリント基板,React,TypeScript,TSX,JSX,Props,型定義,ジェネリック,イベントハンドラ,useState,useReducer,useRef,children,ReactNode,コンポーネント,フロントエンド |description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux |image=/resources/assets/MochiuLogo_Single_Blue.png }} __FORCETOC__ [[カテゴリ:Rust]][[カテゴリ:Web]]
Reactの基礎 - TSXの基本構文
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse