メインコンテンツへスキップ

コンポーネントプロップスの型指定

これは、TypeScriptに精通しているReact開発者向けの、基本的なオリエンテーションとリファレンスとして意図されています。

基本的なプロップスタイプの例

React+TypeScriptアプリケーションで使用する可能性のあるTypeScript型のリスト

type AppProps = {
message: string;
count: number;
disabled: boolean;
/** array of a type! */
names: string[];
/** string literals to specify exact string values, with a union type to join them together */
status: "waiting" | "success";
/** an object with known properties (but could have more at runtime) */
obj: {
id: string;
title: string;
};
/** array of objects! (common) */
objArr: {
id: string;
title: string;
}[];
/** any non-primitive value - can't access any properties (NOT COMMON but useful as placeholder) */
obj2: object;
/** an interface with no required properties - (NOT COMMON, except for things like `React.Component<{}, State>`) */
obj3: {};
/** a dict object with any number of properties of the same type */
dict1: {
[key: string]: MyTypeHere;
};
dict2: Record<string, MyTypeHere>; // equivalent to dict1
/** function that doesn't take or return anything (VERY COMMON) */
onClick: () => void;
/** function with named prop (VERY COMMON) */
onChange: (id: number) => void;
/** function type syntax that takes an event (VERY COMMON) */
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
/** alternative function type syntax that takes an event (VERY COMMON) */
onClick(event: React.MouseEvent<HTMLButtonElement>): void;
/** any function as long as you don't invoke it (not recommended) */
onSomething: Function;
/** an optional prop (VERY COMMON!) */
optional?: OptionalType;
/** when passing down the state setter function returned by `useState` to a child component. `number` is an example, swap out with whatever the type of your state */
setState: React.Dispatch<React.SetStateAction<number>>;
};

プリミティブ型ではない型としての`object`

`object`は、TypeScriptにおけるよくある誤解の種です。「任意のオブジェクト」ではなく、「プリミティブ型ではない任意の型」を意味します。つまり、`number`、`bigint`、`string`、`boolean`、`symbol`、`null`、`undefined`以外のすべての型を表します。

「プリミティブ型ではない任意の値」という型指定は、Reactではあまり行うべきではないため、`object`を頻繁に使用する場面は少ないでしょう。

空インターフェース、`{}`、`Object`

空インターフェース、`{}`、`Object`は、あなたが考えるような「空のオブジェクト」ではなく、「nullishでない任意の値」を表します。これらの型の使用は、よくある誤解の種であり、推奨されません

interface AnyNonNullishValue {} // equivalent to `type AnyNonNullishValue = {}` or `type AnyNonNullishValue = Object`

let value: AnyNonNullishValue;

// these are all fine, but might not be expected
value = 1;
value = "foo";
value = () => alert("foo");
value = {};
value = { foo: "bar" };

// these are errors
value = undefined;
value = null;

便利なReactプロップスタイプの例

他のReactコンポーネントをプロップとして受け取るコンポーネントに関連します。

export declare interface AppProps {
children?: React.ReactNode; // best, accepts everything React can render
childrenElement: React.JSX.Element; // A single React element
style?: React.CSSProperties; // to pass through style props
onChange?: React.FormEventHandler<HTMLInputElement>; // form events! the generic parameter is the type of event.target
// more info: https://react-typescript-cheatsheet.dokyumento.jp/docs/advanced/patterns_by_usecase/#wrappingmirroring
props: Props & React.ComponentPropsWithoutRef<"button">; // to impersonate all the props of a button element and explicitly not forwarding its ref
props2: Props & React.ComponentPropsWithRef<MyButtonWithForwardRef>; // to impersonate all the props of MyButtonForwardedRef and explicitly forwarding its ref
}
React 18以前の小さな`React.ReactNode`の特殊ケース

React 18の型アップデート以前は、このコードは型チェックを通過しましたが、ランタイムエラーが発生していました。

type Props = {
children?: React.ReactNode;
};

function Comp({ children }: Props) {
return <div>{children}</div>;
}
function App() {
// Before React 18: Runtime error "Objects are not valid as a React child"
// After React 18: Typecheck error "Type '{}' is not assignable to type 'ReactNode'"
return <Comp>{{}}</Comp>;
}

これは、`ReactNode`に`ReactFragment`が含まれており、React 18以前は型`{}`を許可していたためです。

この点を指摘してくださった@pomleさんに感謝します。

React.JSX.Element vs React.ReactNode?

引用 @ferdaber:より技術的な説明としては、有効なReactノードは`React.createElement`によって返されるものと同じではありません。コンポーネントが最終的にレンダリングする内容に関係なく、`React.createElement`は常にオブジェクト(`React.JSX.Element`インターフェース)を返しますが、`React.ReactNode`はコンポーネントの可能なすべての戻り値の集合です。

  • `React.JSX.Element` -> `React.createElement`の戻り値
  • `React.ReactNode` -> コンポーネントの戻り値

詳細な議論:ReactNodeとReact.JSX.Elementが重複しない箇所

何か追加する内容がありますか? イシューを報告してください.

型とインターフェース?

プロップスとステートの型指定には、型とインターフェースのどちらかを使用できます。そのため、どちらを使用すべきかという疑問が生じます。

要約

型が必要になるまでインターフェースを使用する - orta

その他のアドバイス

役立つ経験則を以下に示します。

  • ライブラリやサードパーティのアンビエント型定義を作成する際には、常に`interface`を使用して公開APIを定義してください。これにより、一部の定義が不足している場合でも、コンシューマーは *宣言マージ* を通じてそれらを拡張できます。

  • 一貫性のために、またより制約のあるものになるため、Reactコンポーネントのプロップスとステートには`type`を使用することを検討してください。

この経験則の背後にある理由の詳細については、Interface vs Type alias in TypeScript 2.7を参照してください。

TypeScriptハンドブックにも、型エイリアスとインターフェースの違いに関するガイダンスが掲載されています。

注:大規模なプロジェクトでは、インターフェースを優先する方がパフォーマンス上の理由があります(Microsoftの公式ノートを参照)が、鵜呑みしないように注意してください

型はユニオン型(例:`type MyType = TypeA | TypeB`)に便利ですが、インターフェースは辞書の形状を宣言し、それを`implement`または`extend`するのに適しています。

型とインターフェースの便利な比較表

微妙な話題なので、あまり気にしすぎないでください。便利な表を以下に示します。

側面インターフェース
関数を記述できる
コンストラクタを記述できる
タプルを記述できる
インターフェースはそれを拡張できる⚠️
クラスはそれを拡張できる🚫
クラスはそれを実装できる(`implements`)⚠️
同じ種類のものをインターセクトできる⚠️
同じ種類のものをユニオンで作成できる🚫
マップされた型を作成するために使用できる🚫
マップされた型でマップオーバーできる
エラーメッセージとログで展開される🚫
拡張できる🚫
再帰的になれる⚠️

⚠️ 場合によっては

(出典:Karol Majewski)

何か追加する内容がありますか? イシューを報告してください.