React RouterのpropsをTypeScriptで型付けする

2022.02.13

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、CX事業本部 IoT事業部の若槻です。

前回のエントリではReact Routerでprops.historyを使用したルーティングを実装しました。

今回は、このReact RouterのpropsをTypeScriptで型付けしてみました。

やってみた

react-router-domRouteComponentPropsを使用すれば、propsを型付け可能です。

propsによる値の受け渡しがない場合

まず、コンポーネント間でpropsによる値の受け渡しがない場合。

RouteComponentPropsReact.FCにジェネリックで渡すようにすればOKです。

src/Home.tsx

import React from 'react';
import { RouteComponentProps } from 'react-router-dom';

const Home: React.FC<RouteComponentProps> = (props) => {
  return (
    <div>
      <h1>Home page</h1>
      <ul>
        <li>
          <button
            onClick={() => {
              props.history.push({
                pathname: '/profile',
                state: { sourcePage: 'Home' }
              });
            }}
          >
            Go to my profile
          </button>
        </li>
      </ul>
    </div>
  );
};

export default Home;

ソースコードを見ると、React.FCのジェネリックに指定した型は引数propsの型としてそのまま使われているためです。

    type FC<P = {}> = FunctionComponent<P>;

    interface FunctionComponent<P = {}> {
        (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
        propTypes?: WeakValidationMap<P> | undefined;
        contextTypes?: ValidationMap<any> | undefined;
        defaultProps?: Partial<P> | undefined;
        displayName?: string | undefined;
    }

引数に直接RouteComponentPropsを型として指定することもできますが、基本的にReact.FCを使えば良いと思います。

src/Home.tsx

import React from 'react';
import { RouteComponentProps } from 'react-router-dom';

const Home = (props: RouteComponentProps) => {
  return (
    <div>
      中略
    </div>
  );
};

export default Home;

propsによる値の受け渡しがある場合

propsによる値の受け渡しがある場合だと、同じようにRouteComponentPropsを直接React.FCのジェネリックで使用するとエラーとなります。

src/MyProfile.tsx(エラー)

import React from 'react';
import { RouteComponentProps } from 'react-router-dom';

const MyProfile: React.FC<RouteComponentProps> = (
  props
) => {
  return (
    <div>
      <h1>This is my profile</h1>
      <ul>
        {props.location.state && (
          <li>
            <button
              onClick={() => {
                props.history.goBack();
              }}
            >
              Back {props.location.state.sourcePage}
            </button>
          </li>
        )}
      </ul>
    </div>
  );
};

export default MyProfile;

props.location.statesourcePageが無いためエラーとなっています。

ここでRouteComponentPropsのソースコードを見ると、これまたジェネリックでS、つまり第三引数を指定すれば良さげですね。

export interface RouteComponentProps<
    Params extends { [K in keyof Params]?: string } = {},
    C extends StaticContext = StaticContext,
    S = H.LocationState
> {
    history: H.History<S>;
    location: H.Location<S>;
    match: match<Params>;
    staticContext?: C | undefined;
}

というわけで次のようにしたらエラー無く型付けできるようになります。

src/MyProfile.tsx

import React from 'react';
import { RouteComponentProps } from 'react-router-dom';

type Props = RouteComponentProps<
  {},
  {},
  { sourcePage: string }
>;

const MyProfile: React.FC<Props> = (props) => {
  return (
    <div>
      <h1>This is my profile</h1>
      <ul>
        {props.location.state && (
          <li>
            <button
              onClick={() => {
                props.history.goBack();
              }}
            >
              Back {props.location.state.sourcePage}
            </button>
          </li>
        )}
      </ul>
    </div>
  );
};

export default MyProfile;

デモ

ここまでのコードを使用したデモです。

参考

以上