Typecript ve React ile Private Route içinde bileşen proplarını geçir

Aug 18 2020

React Router v4'ten Route bileşeninin render props'larını kullanarak Typescript ve React ile Authenticated Routes uyguluyorum.

Rotalar:

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;

Özel Rota:

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;

Sorun şu ki, proplarda Bileşen setini çağırmak istiyorum, ancak bunu her denediğimde Typescript aşağıdaki hatayı veriyor.

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

Hatanın görüntüsü

Bunun nedeni, Yol için bileşen türünün, burada açıklandığı gibi Typecript'in beklediği gibi olmamasıdır: https://github.com/microsoft/TypeScript/issues/28631, bu nedenle yeni bir türe (ComponentToRender) sahip bir kopya oluşturdum.

Bunu uygulamanın daha iyi bir yolu var mı? Belki RouteProps bileşen öğesinin üzerine yazıyorsunuz?

Teşekkürler!

Yanıtlar

juanjo12x Aug 25 2020 at 16:18

Sonunda hatayı anladım ve çözdüm! Önemli olan, bileşen özniteliği ve oluşturma işlevinin tür bazında farklı şekilde ele alınmasıydı. Kullanarak RouteProps, Typescript derleyicisi aslında bileşen props'larını, index.d.ts dosyasında göründüğü gibi render işlevi için kullanılamayan ComponentType (RouterProps'a göre 'component' için belirtilen tip) olarak ayarlıyor.

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

Bu davranış, yalnızca render işlevini Routes dosyasına taşıyarak ve PrivateRoute bileşenini kullanmadan belirgin hale gelir. Aşağıdaki kod beklendiği gibi çalışır, çünkü derleyici türü doğru bir şekilde belirler (React.ReactNode).

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

Bu nedenle, sorunu çözmek için, yalnızca kullanım durumum için gerekli parametreleri içeren yeni bir tür oluşturuyorum.

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;