LoginSignup
40
43

More than 5 years have passed since last update.

【React + TypeScript】コンポーネントのdefaultPropsの型をきちんと指定する

Last updated at Posted at 2019-02-27

環境情報

  • TypeScript: 3.2.2
  • React: 16.7.0

前提知識: デフォルトプロパティの定義

Class Componentにおけるデフォルトプロパティを定義する際は、下記の通りdefaultPropsフィールドを用意してあげます。

class SomeComponent extends React.Component<SomeProps, SomeState> {
    public static defaultProps: SomeProps = {
        prop1: "default"
    };
    ...
}

サンプルコンポーネント

今回説明を行うに当たって下記のようなButtonコンポーネントを考えてみましょう。
ButtonコンポーネントはプロパティとしてonClick, color, typeを保持しています。

Button.tsx
interface Props {
  onClick: (e: React.MouseEvent<HTMLElement>) => void;
  color: "blue" | "green" | "red";
  type: "button" | "submit";
}

class Button extends React.Component<Props, {}> {
  /**
   * Render DOM.
   */
  public render() {
    return <button type={this.props.type} style={{ color: this.props.color }} onClick={this.props.onClick} />;
  }
}

このコンポーネントはデフォルトプロパティを定義していないため、Buttonコンポーネントを利用する側がonClick, color, type属性を指定しないとコンパイルエラーとなります。

App.tsx
class App extends React.Component {
  public render() {
    // Type error: Type '{}' is missing the following properties from type 'Readonly<InputProps>': onClick, color, type  TS2739
    return <Button />;
  }
}

デフォルトプロパティを定義

全プロパティのデフォルト値を定義する場合は下記のようにすればOKです。

Button.tsx
interface Props {
  onClick?: (e: React.MouseEvent<HTMLElement>) => void;
  color?: "blue" | "green" | "red";
  type?: "button" | "submit";
}

class Button extends React.Component<Props, {}> {
  /**
   * Default properties.
   */
  public static defaultProps: Props = {
    onClick: _ => console.log("clicked"),
    color: "blue",
    type: "button",
  };
  ...
}

これでButtonコンポーネントを利用する側がonClick, color, type属性を指定しない場合はデフォルト値が適用されるため、コンパイルエラーになることはありません。

App.tsx
class App extends React.Component {
  public render() {
    // OK
    return <Button />;
  }
}

一部のプロパティのみデフォルト値を定義

先ほどは全プロパティのデフォルト値を定義していましたが、一部のプロパティのみデフォルト値を定義したい場合はどうすればいいでしょうか。例えばonClickプロパティはデフォルト値を定義せず、外部から必ず指定させたいというケースです。

defaultPropsの型にPropsを指定しているため、一部の項目のみを定義することはできません。

Button.tsx
interface Props {
  onClick: (e: React.MouseEvent<HTMLElement>) => void;
  color?: "blue" | "green" | "red";
  type?: "button" | "submit";
}

class Button extends React.Component<Props, {}> {
  /**
   * Default properties.
   */
  // Type error: Property 'onClick' is missing in type '{ color: "blue"; type: "button"; }' but required in type 'Props'.  TS2741
  public static defaultProps: Props = {
    color: "blue",
    type: "button",
  };
  ...
}

対応案1: defaultPropsの型をPartialにする

当初はdefaultPropsの型をPartial<Props>とすることで部分的なProps型を指定してあげれば、解決すると思ってました。

Button.tsx
interface Props {
  onClick: (e: React.MouseEvent<HTMLElement>) => void;
  color?: "blue" | "green" | "red";
  type?: "button" | "submit";
}

class Button extends React.Component<Props, {}> {
  /**
   * Default properties.
   */
  public static defaultProps: Partial<Props> = {
    color: "blue",
    type: "button",
  };
  ...
}

しかしPartialを利用すると、onClick属性の型が(e: React.MouseEvent<HTMLElement>) => void | undefinedとなってしまい、onClick属性を指定しなかった場合にコンパイルエラーとならず、予期せぬ実行時エラーを招く結果となります。

App.tsx
class App extends React.Component {
  public render() {
    // コンパイルエラーとならず、onClickにはundefinedが設定されてしまう
    return <Button />;
  }
}

対応案2: defaultPropsの型を明示的に定義せず型推論させる

defaultPropsの型として明示的にPropsを指定していましたが、これをやめ型推論を利用してみます。

Button.tsx
interface Props {
  onClick: (e: React.MouseEvent<HTMLElement>) => void;
  color?: "blue" | "green" | "red";
  type?: "button" | "submit";
}

class Button extends React.Component<Props, {}> {
  /**
   * Default properties.
   */
  public static defaultProps = {
    color: "blue",
    type: "button",
  };
  ...
}

onClick属性を指定しない場合は、コンパイルエラーとなることが確認できます。

App.tsx
class App extends React.Component {
  public render() {
    // Type error: Property 'onClick' is missing in type '{}' but required in type 'Pick<Readonly<{ children?: ReactNode; }> & Readonly<Props>, "children" | "onClick">'.  TS2741
    return <Button />;
  }
}

しかしこの場合、defaultPropsにどんなプロパティでも定義できてしまうため、TypeScriptの恩恵に与ることができません。うっかりタイポしてしまった場合でもコンパイルエラーで気づくことができないのです。

Button.tsx
interface Props {
  onClick: (e: React.MouseEvent<HTMLElement>) => void;
  color?: "blue" | "green" | "red";
  type?: "button" | "submit";
}

class Button extends React.Component<Props, {}> {
  /**
   * Default properties.
   */
  public static defaultProps = {
    color: "blue",
    types: "button", // タイポに気づくことができない
    hoge: "hoge", // 任意のプロパティを追加できてしまう
  };
  ...
}

ではどうすればいいでしょうか。

対応案3: defaultProps用のinterfaceを定義する

defaultProps用のinterfaceを明示的に用意してあげればOKです。

Button.tsx
interface Props {
  onClick: (e: React.MouseEvent<HTMLElement>) => void;
  color?: "blue" | "green" | "red";
  type?: "button" | "submit";
}

interface DefaultProps {
  color: "blue" | "green" | "red";
  type: "button" | "submit";
}

class Button extends React.Component<Props, {}> {
  /**
   * Default properties.
   */
  public static defaultProps: Partial<DefaultProps> = {
    color: "blue",
    type: "button",
  };
  ...
}

onClick属性を指定しない場合は、コンパイルエラーとなることが確認できました。

App.tsx
// Type error: Property 'onClick' is missing in type '{}' but required in type 'Pick<Readonly<{ children?: ReactNode; }> & Readonly<Props>, "children" | "onClick">'.  TS2741
class App extends React.Component {
  public render() {
    return <Button />;
  }
}

ただし、このままではPropsDefaultPropsの定義が重複しておりメンテナンス性が悪いため、PropsDefaultPropsを継承するようにします。

Button.tsx
interface Props extends Partial<DefaultProps> {
  onClick: (e: React.MouseEvent<HTMLElement>) => void;
}

interface DefaultProps {
  color: "blue" | "green" | "red";
  type: "button" | "submit";
}

class Button extends React.Component<Props, {}> {
  /**
   * Default properties.
   */
  public static defaultProps: Partial<DefaultProps> = {
    color: "blue",
    type: "button",
  };
  ...
}

これでばっちりです。

参考

40
43
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40
43