コンポーネントプロップスの型指定
これは、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以前は型`{}`を許可していたためです。
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)