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

defaultProps の型付け

defaultProps は必要ないかもしれません

このツイート にあるように、defaultProps は最終的に非推奨になるでしょう。議論についてはこちらを確認してください

共通認識としては、オブジェクトのデフォルト値を使用することです。

関数コンポーネント

type GreetProps = { age?: number };

const Greet = ({ age = 21 }: GreetProps) => // etc

クラスコンポーネント

type GreetProps = {
age?: number;
};

class Greet extends React.Component<GreetProps> {
render() {
const { age = 21 } = this.props;
/*...*/
}
}

let el = <Greet age={3} />;

defaultProps の型付け

defaultProps の型推論は TypeScript 3.0+ で大幅に改善されましたが、いくつかのエッジケースは依然として問題があります

関数コンポーネント

// using typeof as a shortcut; note that it hoists!
// you can also declare the type of DefaultProps if you choose
// e.g. https://github.com/typescript-cheatsheets/react/issues/415#issuecomment-841223219
type GreetProps = { age: number } & typeof defaultProps;

const defaultProps = {
age: 21,
};

const Greet = (props: GreetProps) => {
// etc
};
Greet.defaultProps = defaultProps;

TS Playground で確認してください

クラスコンポーネントの場合、いくつかの方法があります (Pick ユーティリティ型を使用するなど) が、推奨されるのは props 定義を「逆にする」ことです

type GreetProps = typeof Greet.defaultProps & {
age: number;
};

class Greet extends React.Component<GreetProps> {
static defaultProps = {
age: 21,
};
/*...*/
}

// Type-checks! No type assertions needed!
let el = <Greet age={3} />;
ライブラリ作成者向けの React.JSX.LibraryManagedAttributes のニュアンス

上記の実装はアプリ作成者にとっては問題ありませんが、GreetProps をエクスポートして他の人が利用できるようにしたい場合があります。ここで問題となるのは、GreetProps の定義方法では、defaultProps のために必須ではないにもかかわらず、age が必須の prop になっていることです。

ここで理解すべき点は、GreetProps はコンポーネントの 内部 契約であり、外部 のコンシューマー向けの契約ではないということです。エクスポート専用の型を別途作成するか、React.JSX.LibraryManagedAttributes ユーティリティを利用することができます

// internal contract, should not be exported out
type GreetProps = {
age: number;
};

class Greet extends Component<GreetProps> {
static defaultProps = { age: 21 };
}

// external contract
export type ApparentGreetProps = React.JSX.LibraryManagedAttributes<
typeof Greet,
GreetProps
>;

これは正しく機能しますが、ApparentGreetProps にカーソルを合わせると少し戸惑うかもしれません。このボイラープレートは、後述する ComponentProps ユーティリティで削減できます。

defaultProps を持つコンポーネントの Props の利用

defaultProps を持つコンポーネントは、実際には必須ではないいくつかの必須 prop を持っているように見えるかもしれません。

問題提起

これはあなたがやりたいことです

interface IProps {
name: string;
}
const defaultProps = {
age: 25,
};
const GreetComponent = ({ name, age }: IProps & typeof defaultProps) => (
<div>{`Hello, my name is ${name}, ${age}`}</div>
);
GreetComponent.defaultProps = defaultProps;

const TestComponent = (props: React.ComponentProps<typeof GreetComponent>) => {
return <h1 />;
};

// Property 'age' is missing in type '{ name: string; }' but required in type '{ age: number; }'
const el = <TestComponent name="foo" />;

解決策

React.JSX.LibraryManagedAttributes を適用するユーティリティを定義します

type ComponentProps<T> = T extends
| React.ComponentType<infer P>
| React.Component<infer P>
? React.JSX.LibraryManagedAttributes<T, P>
: never;

const TestComponent = (props: ComponentProps<typeof GreetComponent>) => {
return <h1 />;
};

// No error
const el = <TestComponent name="foo" />;

TS Playground で確認してください

その他の議論と知識

なぜ React.FCdefaultProps を壊すのですか?

議論についてはこちらを確認してください

これは単なる現在の状態であり、将来的に修正される可能性があります。

TypeScript 2.9 以前

TypeScript 2.9 以前では、複数の方法がありますが、これはこれまでで最も良いアドバイスです

type Props = Required<typeof MyComponent.defaultProps> & {
/* additional props here */
};

export class MyComponent extends React.Component<Props> {
static defaultProps = {
foo: "foo",
};
}

以前の推奨事項では、TypeScript の Partial type 機能を使用していました。これは、現在のインターフェースがラップされたインターフェースの部分的なバージョンを満たすことを意味します。これにより、型を変更することなく defaultProps を拡張できます。

interface IMyComponentProps {
firstProp?: string;
secondProp: IPerson[];
}

export class MyComponent extends React.Component<IMyComponentProps> {
public static defaultProps: Partial<IMyComponentProps> = {
firstProp: "default",
};
}

このアプローチの問題点は、React.JSX.LibraryManagedAttributes で動作する型推論で複雑な問題が発生することです。基本的に、このコンポーネントで JSX 式を作成すると、その props がすべてオプションであるとコンパイラが考えるようになります。

@ferdaber によるコメントはこちらこちら を参照してください。

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