如何使用 TypeScript 和 React-Router 4、5 或 6 重写受保护/私有路由?

IT技术 reactjs typescript react-router react-router-dom typescript2.0
2021-04-15 16:21:33

我试图使用 TypeScript<PrivateRoute>在 react-router文档中创建一个描述谁能帮我吗?

react-router 文档中的 privateRoute:

const PrivateRoute = ({ component: Component, ...rest }) => (
  <Route {...rest} render={props => (
    fakeAuth.isAuthenticated ? (
      <Component {...props}/>
    ) : (
      <Redirect to={{pathname: '/login', state: { from: props.location }
   }}/>
  )
 )}/>
)

下面是我的 TypeScript 版本(它不起作用):

const PrivateRoute = (theProps: { path: string, component: React.SFC<RouteComponentProps<any> | undefined> | React.ComponentClass<RouteComponentProps<any> | undefined> }) => {
    return <Route path={theProps.path} render={props => (
        fakeAuth.isAuthenticated ? (
            <React.Component {...theProps} /> <!-- **** It will raise error *** -->
        ) : (
                <Redirect to={{
                    pathname: '/',
                    state: { from: props.location }
                }} />
            )
    )} />
}

<React.Component {...thisProps} />是不对的。错误是:NodeInvocationException: inst.render is not a function TypeError: inst.render is not a function

6个回答

错误可能与打字和渲染中的隐式返回有关。当你解决这个问题时,你最终会得到这样的结果:

const PrivateRoute = ({component, isAuthenticated, ...rest}: any) => {
    const routeComponent = (props: any) => (
        isAuthenticated
            ? React.createElement(component, props)
            : <Redirect to={{pathname: '/login'}}/>
    );
    return <Route {...rest} render={routeComponent}/>;
};

该组件可以这样使用:

<PrivateRoute
    path='/private'
    isAuthenticated={this.props.state.session.isAuthenticated}
    component={PrivateContainer}
/>

上述解决方案有一些缺点。其中之一是你失去了类型安全。

可能扩展Route组件是更好的主意。

import * as React from 'react';
import {Redirect, Route, RouteProps} from 'react-router';

export interface ProtectedRouteProps extends RouteProps {
    isAuthenticated: boolean;
    authenticationPath: string;
}

export class ProtectedRoute extends Route<ProtectedRouteProps> {
    public render() {
        let redirectPath: string = '';
        if (!this.props.isAuthenticated) {
            redirectPath = this.props.authenticationPath;
        }

        if (redirectPath) {
            const renderComponent = () => (<Redirect to={{pathname: redirectPath}}/>);
            return <Route {...this.props} component={renderComponent} render={undefined}/>;
        } else {
            return <Route {...this.props}/>;
        }
    }
}

所以你可以像这样使用组件:

const defaultProtectedRouteProps: ProtectedRouteProps = {
    isAuthenticated: this.props.state.session.isAuthenticated,
    authenticationPath: '/login',
};

<ProtectedRoute
    {...defaultProtectedRouteProps}
    exact={true}
    path='/'
    component={ProtectedContainer}
/>

更新(2019 年 11 月)

如果您更喜欢编写功能组件,您可以以非常相似的方式来完成。这也适用于 React Router 5:

import * as React from 'react';
import { Redirect, Route, RouteProps } from 'react-router';

export interface ProtectedRouteProps extends RouteProps {
  isAuthenticated: boolean;
  isAllowed: boolean;
  restrictedPath: string;
  authenticationPath: string;
}

export const ProtectedRoute: React.FC<ProtectedRouteProps> = props => {
  let redirectPath = '';
  if (!props.isAuthenticated) {
    redirectPath = props.authenticationPath;
  }
  if (props.isAuthenticated && !props.isAllowed) {
    redirectPath = props.restrictedPath;
  }

  if (redirectPath) {
    const renderComponent = () => <Redirect to={{ pathname: redirectPath }} />;
    return <Route {...props} component={renderComponent} render={undefined} />;
  } else {
    return <Route {...props} />;
  }
};

export default ProtectedRoute;

更新(2019 年 12 月)

如果要将用户重定向到用户首先要访问的路径,则需要记住该路径,以便在认证成功后重定向。以下答案将指导您完成:

使用 react-router-dom 成功验证后将用户重定向到他们请求的页面

更新(2021 年 3 月)

上面的解决方案有点过时了。ProtectedRoute 组件可以简单地编写如下:

import { Redirect, Route, RouteProps } from 'react-router';

export type ProtectedRouteProps = {
  isAuthenticated: boolean;
  authenticationPath: string;
} & RouteProps;

export default function ProtectedRoute({isAuthenticated, authenticationPath, ...routeProps}: ProtectedRouteProps) {
  if(isAuthenticated) {
    return <Route {...routeProps} />;
  } else {
    return <Redirect to={{ pathname: authenticationPath }} />;
  }
};

如果您使用 React Router V6,则需要替换RedirectNavigate. 可以在此处找到重定向到原始请求页面的完整示例:

如果我将 ProtectedRoute 连接到 redux 以获取 IsAuthenticated 属性怎么办?它会导致性能问题吗?
2021-05-22 16:21:33
不,我不这么认为。与 的原始用法相比,只有一个 if/else Route
2021-06-06 16:21:33
说得通。谢谢@罗宾
2021-06-06 16:21:33
@Piyush:我不同意您isAuthenticated从 props 中省略 的想法,因为该组件将不再可重用。我建议创建某种路由器容器组件,在其中设置所有路由并绑定状态。
2021-06-11 16:21:33
出色的解决方案@Robin。:) 我会在这里添加我的 2 美分: 1. ProtectedRouteProps 不需要 isAuthenticated 因为它高度依赖于 this.props.state。这意味着每个组件都必须具有该信息。相反,开发人员可以使用某种基于 GlobalState / GlobalStore 或 Mobx 的可观察变量来检测 isAuthenticated(或者,道具不会被传递给
2021-06-19 16:21:33

您仍然可以使用 SFC 表单,我觉得它更简洁一些。只需将您需要的任何props与RouteProps

const PrivateRoute: React.SFC<RouteProps> = ({
  component: Component,
  ...rest
}: {
  component: React.ComponentType<RouteProps>;
}) => (
  <Route
    {...rest}
    render={props =>
      fakeAuth.isAuthenticated 
        ? <Component {...props} /> 
        : <Redirect to="/login" />
    }
  />
);
component应该是 typeReact.ComponentType<RouteComponentProps<any>>而不是React.ComponentType<RouteProps>,不是吗?
2021-05-29 16:21:33

我的私人路线

import React from 'react'
import {Redirect, Route, RouteProps} from 'react-router'

export interface IPrivateRouteProps extends RouteProps {
  isAuth: boolean // is authenticate route
  redirectPath: string // redirect path if don't authenticate route
}

const PrivateRoute: React.FC<IPrivateRouteProps> = (props) => {
   return props.isAuth ? (
    <Route {...props} component={props.component} render={undefined} />
  ) : (
    <Redirect to={{pathname: props.redirectPath}} />
  )
}

export default PrivateRoute

使用

<PrivateRoute isAuth={false} redirectPath="/login" path="/t1">
  <Pages.Profile /> your`s protected page
</PrivateRoute>

这真的帮助了我

import * as React from "react";
import { Route } from "react-router-dom";

interface IProps {
    exact?: boolean;
    path: string;
    component: React.ComponentType<any>;
}

const LoggedOutRoute = ({
    component: Component,
    ...otherProps
}: IProps) => (
    <>
        <header>Logged Out Header</header>
        <Route
            render={otherProps => (
                <>
                    <Component {...otherProps} />
                </>
            )}
        />
        <footer>Logged Out Footer</footer>
    </>
);

export default LoggedOutRoute;

资料来源:https : //medium.com/octopus-wealth/authenticated-routing-with-react-react-router-redux-typescript-677ed49d4bd6

对于 react-router-dom (v6.0.2) ,您可以为PrivateRoute 组件使用以下代码

import { FC } from 'react';
import { useAppSelector } from 'app/hooks';
import { Navigate } from 'react-router-dom';

interface PropType {
    component: React.FC;
}

const PrivateRoute: FC<PropType> = ({ component: Component }) => {
    const { isAuthenticated } = useAppSelector(state => state.auth);

    if (isAuthenticated) return <Component />;
    return <Navigate to='/login' />;
};

export default PrivateRoute;

要在您的App.tsx中使用,您可以按如下方式使用它:

        <Routes>
            <Route path='/' element={<LandingPage />} />
            <Route path='/login' element={<LoginPage />} />
            <Route path='/home' element={<PrivateRoute component={HomePage} />} />
            <Route path='*' element={<NotFound />} />
        </Routes>