TypescriptとReactを使用してプライベートルートでコンポーネントの小道具を渡す

Aug 18 2020

React Router v4のRouteコンポーネントのレンダリングプロップを使用して、TypescriptとReactで認証済みルートを実装しています。

ルート:

import React from 'react';
import { Switch, Route } from 'react-router-dom';
import { ROUTES } from 'utils/constants';
import HomePage from 'components/pages/Home';
import GuestLogin from 'components/pages/GuestLogin';
import ProfilePage from 'components/pages/Profile';
import NotFoundPage from 'components/pages/NotFound';
import ResetPassword from 'components/pages/ResetPassword';
import SetPassword from 'components/pages/SetPassword';
import LoginContainer from 'containers/Login';
import PrivateRoute from './PrivateRoute';

const Routes: React.FunctionComponent = () => (
  <Switch>
    <Route path={ROUTES.LOGIN} component={LoginContainer} exact></Route>
    <PrivateRoute
      path={ROUTES.HOME}
      component={HomePage}
    ></PrivateRoute>
    <Route path={ROUTES.GUEST_LOGIN} component={GuestLogin}></Route>
    <Route path={ROUTES.RESET_PASSWORD} component={ResetPassword}></Route>
    <Route path={ROUTES.SET_PASSWORD} component={SetPassword}></Route>
    <Route path={ROUTES.PROFILE} component={ProfilePage}></Route>
    <Route component={NotFoundPage}></Route>
  </Switch>
);

export default Routes;

プライベートルート:

import React from 'react';    
import { useAppContext } from 'containers/App/AppContext';
import { RouteProps, Route, Redirect } from 'react-router-dom';
import { ROUTES } from 'utils/constants';

const PrivateRoute: React.FunctionComponent<RouteProps> = ({
  component: Component,
  ...routeProps
}) => {
  const { isSignedIn } = useAppContext();
  const ComponentToRender = Component as React.ElementType;
  return (
    <Route
      {...routeProps}
      render={(props) =>
        isSignedIn ? (
          <ComponentToRender {...props} />
        ) : (
          <Redirect to={ROUTES.LOGIN} />
        )
      }
    />
  );
};

export default PrivateRoute;

問題は、小道具に設定されたコンポーネントを呼び出したいということですが、これを試みるたびに、Typescriptは次のエラーをスローします。

JSX element type 'Component' does not have any construct or call signatures.  TS2604

エラーの画像

その理由は、ルートのコンポーネントタイプが、ここで説明されているようにTypescriptが期待するものではないためと思われます。 https://github.com/microsoft/TypeScript/issues/28631したがって、新しいタイプ(ComponentToRender)を持つコピーを作成しました。

これを実装するためのより良い方法はありますか?たぶん、RoutePropsコンポーネント要素を上書きしますか?

ありがとう!

回答

juanjo12x Aug 25 2020 at 16:18

私はついにエラーを理解し、それを解決しました!重要なのは、コンポーネント属性とレンダリング関数がタイプごとに異なる方法で処理されることでした。を使用することによりRouteProps、Typescriptコンパイラは実際にコンポーネントの小道具をComponentType(RouterPropsに従って「component」に指定されたタイプ)として設定します。これは、index.d.tsファイルにあるようにレンダリング関数には使用できません。

export interface RouteProps {
    location?: H.Location;
    component?: React.ComponentType<RouteComponentProps<any>> | React.ComponentType<any>;
    render?: (props: RouteComponentProps<any>) => React.ReactNode;
    children?: ((props: RouteChildrenProps<any>) => React.ReactNode) | React.ReactNode;
    path?: string | string[];
    exact?: boolean;
    sensitive?: boolean;
    strict?: boolean;
}

この動作は、PrivateRouteコンポーネントを使用せずに、レンダリング関数をRoutesファイルに移動するだけで明らかになります。コンパイラーが型を正しく推測するため(React.ReactNode)、以下のコードは期待どおりに機能します。

<Route
  path={ROUTES.POINTS_HISTORY}
  render={(props) =>
    isSignedIn ? <PointsTransactions /> : <Redirect to={ROUTES.LOGIN} />
  }
></Route>

したがって、この問題を解決するために、ユースケースに必要なパラメーターのみを使用して新しい型を作成するだけです。

import React from 'react';

import { useAppContext } from 'containers/App/AppContext';
import { RouteProps, Route, Redirect } from 'react-router-dom';
import { ROUTES } from 'utils/constants';

type PrivateRouteProps = {
  path: RouteProps['path'];
  component: React.ElementType;
};
const PrivateRoute: React.FunctionComponent<PrivateRouteProps> = ({
  component: Component,
  ...routeProps
}) => {
  const { isSignedIn } = useAppContext();
  return (
    <Route
      {...routeProps}
      render={(props) =>
        isSignedIn ? <Component /> : <Redirect to={ROUTES.LOGIN} />
      }
    />
  );
};

export default PrivateRoute;