使用 Typescript 和 React 在私有路由中传递组件道具

jua*_*12x 3 typescript reactjs react-router

我正在使用来自 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;
Run Code Online (Sandbox Code Playgroud)

私人路线:

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;
Run Code Online (Sandbox Code Playgroud)

问题是我想调用 props 上的 Component 集,但是,每次我尝试这样做时,Typescript 都会抛出以下错误。

JSX element type 'Component' does not have any construct or call signatures.  TS2604
Run Code Online (Sandbox Code Playgroud)

错误图像

原因似乎是 Route 的组件类型不是 Typescript 所期望的类型,如下所述:https : //github.com/microsoft/TypeScript/issues/28631,因此我刚刚创建了一个具有新类型的副本(组件渲染)。

有没有更好的方法来实现这一点?也许覆盖 RouteProps 组件元素?

谢谢!

jua*_*12x 5

我终于明白了错误并解决了它!关键是组件属性和渲染函数的处理方式不同。通过使用RouteProps,Typescript 编译器实际上将组件 props 设置为 ComponentType(根据 RouterProps 为“组件”指定的类型),它不能用于渲染功能,如 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;
}
Run Code Online (Sandbox Code Playgroud)

仅通过将渲染函数移动到 Routes 文件而不使用 PrivateRoute 组件,这种行为就会变得明显。下面的代码按预期工作,因为编译器正确推断了类型 (React.ReactNode)。

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

因此,为了解决这个问题,我只为我的用例创建了一个只有必要参数的新类型。

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;
Run Code Online (Sandbox Code Playgroud)