Übergeben Sie Komponentenrequisiten in Private Route mit Typescript und React

Aug 18 2020

Ich implementiere authentifizierte Routen mit Typescript und React mithilfe der Render-Requisiten der Route-Komponente von React Router v4.

Routen:

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;

Privatweg:

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;

Das Problem ist, dass ich den Komponentensatz auf den Requisiten aufrufen möchte. Jedes Mal, wenn ich dies versuche, gibt Typescript den folgenden Fehler aus.

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

Bild des Fehlers

Der Grund scheint zu sein, dass der Komponententyp für die Route nicht derjenige ist, den Typescript erwartet, wie hier erläutert: https://github.com/microsoft/TypeScript/issues/28631Deshalb habe ich gerade eine Kopie erstellt, die einen neuen Typ hat (ComponentToRender).

Gibt es einen besseren Weg, dies umzusetzen? Möglicherweise das RouteProps-Komponentenelement überschreiben?

Vielen Dank!

Antworten

juanjo12x Aug 25 2020 at 16:18

Ich habe den Fehler endlich verstanden und behoben! Der Schlüssel war, dass das Komponentenattribut und die Renderfunktion typabhängig behandelt werden. Durch die Verwendung RoutePropssetzt der Typescript-Compiler die Komponenten-Requisiten tatsächlich als ComponentType (der Typ, der gemäß RouterProps für 'Komponente' angegeben wurde), der nicht für die Renderfunktion verwendet werden kann, wie in der Datei index.d.ts angegeben.

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;
}

Dieses Verhalten wird nur dadurch deutlich, dass die Renderfunktion in die Routendatei verschoben wird und die PrivateRoute-Komponente nicht verwendet wird. Der folgende Code funktioniert wie erwartet, da der Compiler den Typ korrekt ableitet (React.ReactNode).

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

Um das Problem zu lösen, erstelle ich einfach einen neuen Typ mit nur den erforderlichen Parametern für meinen Anwendungsfall.

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;