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

ユースケース別のお役立ちパターン

ラッピング/ミラーリング

HTML 要素のラッピング/ミラーリング

ユースケース: <button> の通常の props をすべて受け取り、追加の処理を行う <Button> を作成したい場合。

戦略: React.ComponentPropsWithoutRef<'button'> を拡張する

// usage
function App() {
// Type '"foo"' is not assignable to type '"button" | "submit" | "reset" | undefined'.(2322)
// return <Button type="foo"> sldkj </Button>

// no error
return <Button type="button"> text </Button>;
}

// implementation
export interface ButtonProps extends React.ComponentPropsWithoutRef<"button"> {
specialProp?: string;
}
export function Button(props: ButtonProps) {
const { specialProp, ...rest } = props;
// do something with specialProp
return <button {...rest} />;
}

TS Playground でこれを確認する

Ref の転送: ほとんどのユースケースでは、内部要素への ref を取得する必要はありません。ただし、再利用可能なコンポーネントライブラリを構築する場合、親コンポーネントに内部コンポーネントの基になる DOM ノードを公開するために、forwardRef が必要なことがよくあります。次に、ComponentPropsWithRef を使用して、ラッパーコンポーネントの props を取得できます。詳細については、Ref の転送に関するドキュメント を確認してください。

ComponentPropsIntrinsicElements[Element]HTMLAttributesHTMLProps、または HTMLAttributes を使用しないのはなぜですか?

ComponentProps

ComponentPropsWithRef の代わりに ComponentProps を使用することもできますが、コンポーネントの ref が転送されるかどうかを明示的にしたい場合は、この方法が適しているかもしれません。トレードオフは、やや難しい用語になることです。

詳細情報: https://react-typescript-cheatsheet.dokyumento.jp/docs/basic/getting-started/forward_and_create_ref/

もしかしたら React.JSX.IntrinsicElements または [Element]HTMLAttributes

これを実現するための同等の方法は少なくとも他にも 2 つありますが、より冗長です。

// Method 1: React.JSX.IntrinsicElements
type BtnType = React.JSX.IntrinsicElements["button"]; // cannot inline or will error
export interface ButtonProps extends BtnType {} // etc

// Method 2: React.[Element]HTMLAttributes
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>

ComponentProps のソース を見ると、これは React.JSX.IntrinsicElements の賢いラッパーであることがわかります。一方、2 番目の方法は、なじみのない命名規則/大文字化を持つ特殊なインターフェースに依存しています。

注意: このような特殊なインターフェースは 50 以上あります。@types/react の解説 の HTMLAttributes を参照してください。

最終的に、ComponentProps メソッドを選択 しました。これは、TypeScript 固有の専門用語が最も少なく、最も使いやすいためです。ただし、どちらの方法でも問題なく使用できます。

決して React.HTMLPropsReact.HTMLAttributes は使用しない

React.HTMLProps を使用すると、次のようになります。

export interface ButtonProps extends React.HTMLProps<HTMLButtonElement> {
specialProp: string;
}
export function Button(props: ButtonProps) {
const { specialProp, ...rest } = props;
// ERROR: Type 'string' is not assignable to type '"button" | "submit" | "reset" | undefined'.
return <button {...rest} />;
}

内部で AllHTMLAttributes を使用している ため、type に対して広すぎる型 string を推論します。

React.HTMLAttributes を使用すると、次のようになります。

import { HTMLAttributes } from "react";

export interface ButtonProps extends HTMLAttributes<HTMLButtonElement> {
/* etc */
}

function App() {
// Property 'type' does not exist on type 'IntrinsicAttributes & ButtonProps'
return <Button type="submit"> text </Button>;
}

コンポーネントのラッピング/ミラーリング

TODO: このセクションは、簡略化するために手直しが必要です。

ユースケース: 上記と同じですが、基になる props にアクセスできない React コンポーネントの場合。

import { CSSProperties } from "react";

const Box = (props: CSSProperties) => <div style={props} />;

const Card = (
{ title, children, ...props }: { title: string } & $ElementProps<typeof Box> // new utility, see below
) => (
<Box {...props}>
{title}: {children}
</Box>
);

戦略: props を推論してコンポーネントの props を抽出する

// ReactUtilityTypes.d.ts
declare type $ElementProps<T> = T extends React.ComponentType<infer Props>
? Props extends object
? Props
: never
: never;

使用方法

import * as Recompose from "recompose";
export const defaultProps = <
C extends React.ComponentType,
D extends Partial<$ElementProps<C>>
>(
defaults: D,
Component: C
): React.ComponentType<$ElementProps<C> & Partial<D>> =>
Recompose.defaultProps(defaults)(Component);

thanks dmisdm

🆕 明示的に ref を転送するかどうかも検討する必要があります

import { forwardRef, ReactNode } from "react";

// base button, with ref forwarding
type Props = { children: ReactNode; type: "submit" | "button" };
export type Ref = HTMLButtonElement;

export const FancyButton = forwardRef<Ref, Props>((props, ref) => (
<button ref={ref} className="MyCustomButtonClass" type={props.type}>
{props.children}
</button>
));

ポリモーフィックコンポーネント(例: as props を使用)

「ポリモーフィックコンポーネント」= レンダリングされるコンポーネントを渡すこと。例:as props を使用する。

ElementType は、createElement に渡すことができるほとんどの型をカバーするのに非常に役立ちます。例:

function PassThrough(props: { as: React.ElementType<any> }) {
const { as: Component } = props;

return <Component />;
}

React Router でもこれが見られることがあります。

const PrivateRoute = ({ component: Component, ...rest }: PrivateRouteProps) => {
const { isLoggedIn } = useAuth();

return isLoggedIn ? <Component {...rest} /> : <Redirect to="/" />;
};

詳細については、次のリソースを参照してください。

@eps1lon@karol-majewski のご意見に感謝します!

ジェネリックコンポーネント

TypeScript でジェネリック関数やクラスを作成できるのと同様に、再利用可能な型安全性を実現するために、ジェネリックコンポーネントを作成することもできます。Props と State の両方が同じジェネリック型を利用できますが、State よりも Props で利用する方が理にかなっているでしょう。次に、ジェネリック型を使用して、関数/クラススコープ内で定義された任意の変数の型にアノテーションを付けることができます。

import { ReactNode, useState } from "react";

interface Props<T> {
items: T[];
renderItem: (item: T) => ReactNode;
}

function List<T>(props: Props<T>) {
const { items, renderItem } = props;
const [state, setState] = useState<T[]>([]); // You can use type T in List function scope.
return (
<div>
{items.map(renderItem)}
<button onClick={() => setState(items)}>Clone</button>
{JSON.stringify(state, null, 2)}
</div>
);
}

次に、ジェネリックコンポーネントを使用して、型推論を通じて優れた型安全性を得ることができます。

ReactDOM.render(
<List
items={["a", "b"]} // type of 'string' inferred
renderItem={(item) => (
<li key={item}>
{/* Error: Property 'toPrecision' does not exist on type 'string'. */}
{item.toPrecision(3)}
</li>
)}
/>,
document.body
);

TS 2.9 以降では、型推論をオプトアウトするために JSX で型パラメータを指定することもできます。

ReactDOM.render(
<List<number>
items={["a", "b"]} // Error: Type 'string' is not assignable to type 'number'.
renderItem={(item) => <li key={item}>{item.toPrecision(3)}</li>}
/>,
document.body
);

fat arrow 関数スタイルを使用してジェネリックを使用することもできます。

import { ReactNode, useState } from "react";

interface Props<T> {
items: T[];
renderItem: (item: T) => ReactNode;
}

// Note the <T extends unknown> before the function definition.
// You can't use just `<T>` as it will confuse the TSX parser whether it's a JSX tag or a Generic Declaration.
// You can also use <T,> https://github.com/microsoft/TypeScript/issues/15713#issuecomment-499474386
const List = <T extends unknown>(props: Props<T>) => {
const { items, renderItem } = props;
const [state, setState] = useState<T[]>([]); // You can use type T in List function scope.
return (
<div>
{items.map(renderItem)}
<button onClick={() => setState(items)}>Clone</button>
{JSON.stringify(state, null, 2)}
</div>
);
};

クラスを使用する場合も同様です。(クレジット:Karol Majewskigist

import { PureComponent, ReactNode } from "react";

interface Props<T> {
items: T[];
renderItem: (item: T) => ReactNode;
}

interface State<T> {
items: T[];
}

class List<T> extends PureComponent<Props<T>, State<T>> {
// You can use type T inside List class.
state: Readonly<State<T>> = {
items: [],
};
render() {
const { items, renderItem } = this.props;
// You can use type T inside List class.
const clone: T[] = items.slice(0);
return (
<div>
{items.map(renderItem)}
<button onClick={() => this.setState({ items: clone })}>Clone</button>
{JSON.stringify(this.state, null, 2)}
</div>
);
}
}

ただし、Static メンバーにジェネリック型パラメータを使用することはできません。

class List<T> extends React.PureComponent<Props<T>, State<T>> {
// Static members cannot reference class type parameters.ts(2302)
static getDerivedStateFromProps(props: Props<T>, state: State<T>) {
return { items: props.items };
}
}

これを修正するには、静的関数を型推論された関数に変換する必要があります。

class List<T> extends React.PureComponent<Props<T>, State<T>> {
static getDerivedStateFromProps<T>(props: Props<T>, state: State<T>) {
return { items: props.items };
}
}

子の型付け

一部の API 設計では、親コンポーネントに渡される children にいくつかの制限が必要です。型でこれらを強制することが一般的ですが、この機能には制限があることに注意する必要があります。

できること

子要素の構造を型指定できます: 1 つの子要素、または子要素のタプル。

以下は有効です。

type OneChild = React.ReactNode;
type TwoChildren = [React.ReactNode, React.ReactNode];
type ArrayOfProps = SomeProp[];
type NumbersChildren = number[];
type TwoNumbersChildren = [number, number];
TS で失敗した場合は、`prop-types` も使用できることを忘れないでください。
Parent.propTypes = {
children: PropTypes.shape({
props: PropTypes.shape({
// could share `propTypes` to the child
value: PropTypes.string.isRequired,
}),
}).isRequired,
};

できないこと

できないことは、子要素がどのコンポーネントであるかを指定することです。たとえば、TypeScript で「React Router <Routes> は子要素として <Route> のみを持つことができ、他のものは許可されない」という事実を表現したい場合です。

これは、JSX 式 (const foo = <MyComponent foo='foo' />) を記述すると、結果の型はジェネリックな React.JSX.Element 型にブラックボックス化されるためです。 (@ferdaber に感謝します)

Props に基づく型の絞り込み

やりたいこと

// Usage
function App() {
return (
<>
{/* 😎 All good */}
<Button target="_blank" href="https://www.google.com">
Test
</Button>
{/* 😭 Error, `disabled` doesnt exist on anchor element */}
<Button disabled href="x">
Test
</Button>
</>
);
}

実装方法: 型ガード を使用してください!

// Button props
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
href?: undefined;
};

// Anchor props
type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement> & {
href?: string;
};

// Input/output options
type Overload = {
(props: ButtonProps): React.JSX.Element;
(props: AnchorProps): React.JSX.Element;
};

// Guard to check if href exists in props
const hasHref = (props: ButtonProps | AnchorProps): props is AnchorProps =>
"href" in props;

// Component
const Button: Overload = (props: ButtonProps | AnchorProps) => {
// anchor render
if (hasHref(props)) return <a {...props} />;
// button render
return <button {...props} />;
};

TypeScript Playground で表示

コンポーネント、および一般的に JSX は、関数に類似しています。コンポーネントが props に基づいて異なる方法でレンダリングできる場合、これは関数がオーバーロードされて複数の呼び出しシグネチャを持つことができる方法に似ています。同様に、関数コンポーネントの呼び出しシグネチャをオーバーロードして、異なる「バージョン」をすべてリストできます。

これの非常に一般的なユースケースは、href 属性を受け取るかどうかに基づいて、ボタンまたはアンカーとして何かをレンダリングすることです。

type ButtonProps = React.JSX.IntrinsicElements["button"];
type AnchorProps = React.JSX.IntrinsicElements["a"];

// optionally use a custom type guard
function isPropsForAnchorElement(
props: ButtonProps | AnchorProps
): props is AnchorProps {
return "href" in props;
}

function Clickable(props: ButtonProps | AnchorProps) {
if (isPropsForAnchorElement(props)) {
return <a {...props} />;
} else {
return <button {...props} />;
}
}

プロパティに少なくとも 1 つの違いがある限り、完全に異なる props である必要はありません。

type LinkProps = Omit<React.JSX.IntrinsicElements["a"], "href"> & {
to?: string;
};

function RouterLink(props: LinkProps | AnchorProps) {
if ("href" in props) {
return <a {...props} />;
} else {
return <Link {...props} />;
}
}
アプローチ: ジェネリックコンポーネント

以下は解決策の例です。他の解決策については、今後の議論を参照してください。@jpavon に感謝します

interface LinkProps {}
type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement>;
type RouterLinkProps = Omit<NavLinkProps, "href">;

const Link = <T extends {}>(
props: LinkProps & T extends RouterLinkProps ? RouterLinkProps : AnchorProps
) => {
if ((props as RouterLinkProps).to) {
return <NavLink {...(props as RouterLinkProps)} />;
} else {
return <a {...(props as AnchorProps)} />;
}
};

<Link<RouterLinkProps> to="/">My link</Link>; // ok
<Link<AnchorProps> href="/">My link</Link>; // ok
<Link<RouterLinkProps> to="/" href="/">
My link
</Link>; // error
アプローチ: 構成

コンポーネントを条件付きでレンダリングする場合は、React の構成モデル を使用して、よりシンプルなコンポーネントを作成し、型付けを理解しやすくする方が適切な場合があります。

type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement>;
type RouterLinkProps = Omit<AnchorProps, "href">;

interface ButtonProps {
as: React.ComponentClass | "a";
children?: React.ReactNode;
}

const Button: React.FunctionComponent<ButtonProps> = (props) => {
const { as: Component, children, ...rest } = props;
return (
<Component className="button" {...rest}>
{children}
</Component>
);
};

const AnchorButton: React.FunctionComponent<AnchorProps> = (props) => (
<Button as="a" {...props} />
);

const LinkButton: React.FunctionComponent<RouterLinkProps> = (props) => (
<Button as={NavLink} {...props} />
);

<LinkButton to="/login">Login</LinkButton>;
<AnchorButton href="/login">Login</AnchorButton>;
<AnchorButton href="/login" to="/test">
Login
</AnchorButton>; // Error: Property 'to' does not exist on type...

判別共用体を使用することもできます。判別共用体を使用した表現力豊かな React コンポーネント API を確認してください。

判別共用体型 の簡単な直感を示します。

type UserTextEvent = {
type: "TextEvent";
value: string;
target: HTMLInputElement;
};
type UserMouseEvent = {
type: "MouseEvent";
value: [number, number];
target: HTMLElement;
};
type UserEvent = UserTextEvent | UserMouseEvent;
function handle(event: UserEvent) {
if (event.type === "TextEvent") {
event.value; // string
event.target; // HTMLInputElement
return;
}
event.value; // [number, number]
event.target; // HTMLElement
}
注意: TypeScript は、typeof チェックに基づいて判別共用体の型を絞り込みません。型ガードは、キーの値に基づいて行う必要があり、その型に基づいて行うことはできません。
type UserTextEvent = { value: string; target: HTMLInputElement };
type UserMouseEvent = { value: [number, number]; target: HTMLElement };
type UserEvent = UserTextEvent | UserMouseEvent;
function handle(event: UserEvent) {
if (typeof event.value === "string") {
event.value; // string
event.target; // HTMLInputElement | HTMLElement (!!!!)
return;
}
event.value; // [number, number]
event.target; // HTMLInputElement | HTMLElement (!!!!)
}

上の例は、event.value の値ではなく、その型のみをチェックしているため機能しません。詳細については、microsoft/TypeScript#30506 (コメント) を参照してください。

TypeScriptの判別共用体は、Reactのフック依存関係でも機能します。フックが依存する共用体のメンバーが変更されると、対応する型が自動的に更新されます。詳細を表示して、ユースケースの例をご覧ください。

import { useMemo } from "react";

interface SingleElement {
isArray: true;
value: string[];
}
interface MultiElement {
isArray: false;
value: string;
}
type Props = SingleElement | MultiElement;

function Sequence(p: Props) {
return useMemo(
() => (
<div>
value(s):
{p.isArray && p.value.join(",")}
{!p.isArray && p.value}
</div>
),
[p.isArray, p.value] // TypeScript automatically matches the corresponding value type based on dependency change
);
}

function App() {
return (
<div>
<Sequence isArray={false} value={"foo"} />
<Sequence isArray={true} value={["foo", "bar", "baz"]} />
</div>
);
}
TS Playgroundで確認する

上記の例では、isArray共用体メンバーに基づいて、valueフック依存関係の型が変更されます。

これを効率化するために、ユーザー定義型ガードの概念と組み合わせることもできます。

function isString(a: unknown): a is string {
return typeof a === "string";
}

ユーザー定義型ガードの詳細については、ハンドブックをお読みください。.

extendsを使用した絞り込み

このクイックガイドをご覧ください:https://twitter.com/mpocock1/status/1500813765973053440?s=20&t=ImUA-NnZc4iUuPDx-XiMTA

Props: どちらか一方のみ、両方は不可

TypeScriptは構造的な型システムであるため、最小限のプロパティ要件が記述され、正確な要件ではありません。これにより、想定されるよりも特定のpropsを禁止するのが少し難しくなります。neverを使用すると、どちらか一方のpropのみを許可し、両方は不可にすることができます。

type Props1 = { foo: string; bar?: never };
type Props2 = { bar: string; foo?: never };

const OneOrTheOther = (props: Props1 | Props2) => {
if ("foo" in props && typeof props.foo === "string") {
// `props.bar` is of type `undefined`
return <>{props.foo}</>;
}
// `props.foo` is of type `undefined`
return <>{props.bar}</>;
};
const Component = () => (
<>
<OneOrTheOther /> {/* error */}
<OneOrTheOther foo="" /> {/* ok */}
<OneOrTheOther bar="" /> {/* ok */}
<OneOrTheOther foo="" bar="" /> {/* error */}
</>
);

TypeScript Playground で表示.

より良い代替案は、次のように判別プロパティを使用することです。

type Props1 = { type: "foo"; foo: string };
type Props2 = { type: "bar"; bar: string };

const OneOrTheOther = (props: Props1 | Props2) => {
if (props.type === "foo") {
// `props.bar` does not exist
return <>{props.foo}</>;
}
// `props.foo` does not exist
return <>{props.bar}</>;
};
const Component = () => (
<>
<OneOrTheOther type="foo" /> {/* error */}
<OneOrTheOther type="foo" foo="" /> {/* ok */}
<OneOrTheOther type="foo" bar="" /> {/* error */}
<OneOrTheOther type="foo" foo="" bar="" /> {/* error */}
<OneOrTheOther type="bar" /> {/* error */}
<OneOrTheOther type="bar" foo="" /> {/* error */}
<OneOrTheOther type="bar" bar="" /> {/* ok */}
<OneOrTheOther type="bar" foo="" bar="" /> {/* error */}
</>
);

TypeScript Playground で表示.

Props: すべてまたは何も渡さない

propsを渡さないことは、空のオブジェクトを渡すことと同等です。ただし、空のオブジェクトの型は、想定される{}ではありません。空のインターフェース、{}、およびObjectの意味を必ず理解してくださいRecord<string, never>は、空のオブジェクト型に最も近い可能性があり、typescript-eslintによって推奨されています。以下は、「何も渡さない、またはすべて渡す」ことを許可する例です。

interface All {
a: string;
b: string;
}

type Nothing = Record<string, never>;

const AllOrNothing = (props: All | Nothing) => {
if ("a" in props) {
return <>{props.b}</>;
}
return <>Nothing</>;
};

const Component = () => (
<>
<AllOrNothing /> {/* ok */}
<AllOrNothing a="" /> {/* error */}
<AllOrNothing b="" /> {/* error */}
<AllOrNothing a="" b="" /> {/* ok */}
</>
);

これは機能しますが、Record<string, never>で空のオブジェクトを表現することは、公式には推奨されていません。「正確に空のオブジェクト」を型付けしようとするのを避けるために、別の方法でアプローチする方が良いかもしれません。1つの方法は、必須のpropsをオプションのオブジェクトにグループ化することです。

interface Props {
obj?: {
a: string;
b: string;
};
}

const AllOrNothing = (props: Props) => {
if (props.obj) {
return <>{props.obj.a}</>;
}
return <>Nothing</>;
};

const Component = () => (
<>
<AllOrNothing /> {/* ok */}
<AllOrNothing obj={{ a: "" }} /> {/* error */}
<AllOrNothing obj={{ b: "" }} /> {/* error */}
<AllOrNothing obj={{ a: "", b: "" }} /> {/* ok */}
</>
);

別の方法は、両方のpropsをオプションにし、ランタイムでpropsがまったく渡されないか、すべて渡されるかをチェックすることです。

Props: 他方が渡された場合にのみ、一方を渡す

truncate propが渡された場合にテキストが切り捨てられ、expanded propが渡された場合(ユーザーがテキストをクリックした場合など)に完全なテキストを表示するように拡張されるTextコンポーネントが必要だとします。

expandedは、テキストが切り捨てられていない場合は使用されないため、truncateも渡された場合にのみ渡すことを許可する必要があります。

使用例

const App = () => (
<>
{/* these all typecheck */}
<Text>not truncated</Text>
<Text truncate>truncated</Text>
<Text truncate expanded>
truncate-able but expanded
</Text>
{/* TS error: Property 'truncate' is missing in type '{ children: string; expanded: true; }' but required in type '{ truncate: true; expanded?: boolean | undefined; }'. */}
<Text expanded>truncate-able but expanded</Text>
</>
);

これは、関数のオーバーロードによって実装できます。

import { ReactNode } from "react";

interface CommonProps {
children?: ReactNode;
miscProps?: any;
}

type NoTruncateProps = CommonProps & { truncate?: false };

type TruncateProps = CommonProps & { truncate: true; expanded?: boolean };

// Function overloads to accept both prop types NoTruncateProps & TruncateProps
function Text(props: NoTruncateProps): React.JSX.Element;
function Text(props: TruncateProps): React.JSX.Element;
function Text(props: CommonProps & { truncate?: boolean; expanded?: boolean }) {
const { children, truncate, expanded, ...otherProps } = props;
const classNames = truncate ? ".truncate" : "";
return (
<div className={classNames} aria-expanded={!!expanded} {...otherProps}>
{children}
</div>
);
}

Props: 型からpropを削除する

注:OmitはTS 3.5でファーストクラスのユーティリティとして追加されました!🎉

型を交差させるとき、独自のバージョンのpropを定義したい場合があります。たとえば、コンポーネントにlabelを持たせたいが、交差させている型にもlabel propがある場合です。これを抽出する方法を次に示します。

export interface Props {
label: React.ReactNode; // this will conflict with the InputElement's label
}

// this comes inbuilt with TS 3.5
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

// usage
export const Checkbox = (
props: Props & Omit<React.HTMLProps<HTMLInputElement>, "label">
) => {
const { label } = props;
return (
<div className="Checkbox">
<label className="Checkbox-label">
<input type="checkbox" {...props} />
</label>
<span>{label}</span>
</div>
);
};

コンポーネントが複数のpropsを定義する場合、それらの競合の可能性が高まります。ただし、keyof演算子を使用すると、基になるコンポーネントからすべてのフィールドを削除する必要があることを明示的に示すことができます。

export interface Props {
label: React.ReactNode; // conflicts with the InputElement's label
onChange: (text: string) => void; // conflicts with InputElement's onChange
}

export const Textbox = (
props: Props & Omit<React.HTMLProps<HTMLInputElement>, keyof Props>
) => {
// implement Textbox component ...
};

上記のOmitの例からわかるように、型に重要なロジックを記述することもできます。type-zooは、確認することをお勧めする演算子の優れたツールキット(Omitを含む)であり、utility-types(特にFlowから移行する人向け)も同様です。

Props: コンポーネントのProp型を抽出する

コンポーネントのprop型が必要な場合でも、エクスポートされていない場合があります。

簡単な解決策は、React.ComponentPropsを使用することです。

// a Modal component defined elsewhere
const defaultProps: React.ComponentProps<typeof Modal> = {
title: "Hello World",
visible: true,
onClick: jest.fn(),
};

内部props、propTypesdefaultPropsを考慮してコンポーネントのprop型を抽出する場合、高度なエッジケースがあります。これらを解決するヘルパーユーティリティについては、こちらの問題をご確認ください

Props: Render Props

アドバイス:可能な限り、Render Propsの代わりにHooksを使用するようにしてください。これは単に完全性のためだけに含めています。

React要素または文字列などをpropとして受け取ることができる関数を書きたい場合があります。このような状況に最適な型は、通常の、つまりReactノードが収まる場所に適合するReactNodeです。

import { ReactNode } from "react";

interface Props {
label?: ReactNode;
children?: ReactNode;
}

const Card = ({ children, label }: Props) => {
return (
<div>
{label && <div>{label}</div>}
{children}
</div>
);
};

関数を子として使用するレンダーpropを使用している場合

import { ReactNode } from "react";

interface Props {
children: (foo: string) => ReactNode;
}

何か追加しますか?問題を提出してください.

例外の処理

悪いことが起こったときに、適切な情報を提供できます。

class InvalidDateFormatError extends RangeError {}
class DateIsInFutureError extends RangeError {}

/**
* // optional docblock
* @throws {InvalidDateFormatError} The user entered date incorrectly
* @throws {DateIsInFutureError} The user entered date in future
*
*/
function parse(date: string) {
if (!isValid(date))
throw new InvalidDateFormatError("not a valid date format");
if (isInFuture(date)) throw new DateIsInFutureError("date is in the future");
// ...
}

try {
// call parse(date) somewhere
} catch (e) {
if (e instanceof InvalidDateFormatError) {
console.error("invalid date format", e);
} else if (e instanceof DateIsInFutureError) {
console.warn("date is in future", e);
} else {
throw e;
}
}

TypeScript Playgroundで表示

例外をスローするだけで十分ですが、TypeScriptに、コードのコンシューマーに例外を処理するように注意させることができれば便利です。スローするのではなく、返すだけでそれを行うことができます。

function parse(
date: string
): Date | InvalidDateFormatError | DateIsInFutureError {
if (!isValid(date))
return new InvalidDateFormatError("not a valid date format");
if (isInFuture(date)) return new DateIsInFutureError("date is in the future");
// ...
}

// now consumer *has* to handle the errors
let result = parse("mydate");
if (result instanceof InvalidDateFormatError) {
console.error("invalid date format", result.message);
} else if (result instanceof DateIsInFutureError) {
console.warn("date is in future", result.message);
} else {
/// use result safely
}

// alternately you can just handle all errors
if (result instanceof Error) {
console.error("error", result);
} else {
/// use result safely
}

TryOption(またはMaybe)、Eitherなどの特別な目的のデータ型で例外を記述することもできます(モナドとは言わないでください...)。

interface Option<T> {
flatMap<U>(f: (value: T) => None): None;
flatMap<U>(f: (value: T) => Option<U>): FormikOption<U>;
getOrElse(value: T): T;
}
class Some<T> implements Option<T> {
constructor(private value: T) {}
flatMap<U>(f: (value: T) => None): None;
flatMap<U>(f: (value: T) => Some<U>): Some<U>;
flatMap<U>(f: (value: T) => Option<U>): Option<U> {
return f(this.value);
}
getOrElse(): T {
return this.value;
}
}
class None implements Option<never> {
flatMap<U>(): None {
return this;
}
getOrElse<U>(value: U): U {
return value;
}
}

// now you can use it like:
let result = Option(6) // Some<number>
.flatMap((n) => Option(n * 3)) // Some<number>
.flatMap((n = new None())) // None
.getOrElse(7);

// or:
let result = ask() // Option<string>
.flatMap(parse) // Option<Date>
.flatMap((d) => new Some(d.toISOString())) // Option<string>
.getOrElse("error parsing string");