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

新手上路,请多包涵

我试图创建一个 <PrivateRoute> 如使用 TypeScript 的 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 不是函数 TypeError:inst.render 不是函数

原文由 Charlie 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 578
2 个回答

原始答案(2017)

(以下更新答案)

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

 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,您需要将 Redirect 替换为 Navigate 。可以在此处找到重定向到最初请求页面的完整示例:

更新(2022 年 1 月)

作为 <Routes> 的孩子需要是 <Route> 元素 <ProtectedRoute> 可以更改为:

 export type ProtectedRouteProps = {
  isAuthenticated: boolean;
  authenticationPath: string;
  outlet: JSX.Element;
};

export default function ProtectedRoute({isAuthenticated, authenticationPath, outlet}: ProtectedRouteProps) {
  if(isAuthenticated) {
    return outlet;
  } else {
    return <Navigate to={{ pathname: authenticationPath }} />;
  }
};

<ProtectedRoute> 现在可以如下应用:

 const defaultProtectedRouteProps: Omit<ProtectedRouteProps, 'outlet'> = {
  isAuthenticated: !!sessionContext.isAuthenticated,
  authenticationPath: '/login',
};

return (
  <div>
    <Routes>
      <Route path='/' element={<Homepage />} />
      <Route path='dashboard' element={<ProtectedRoute {...defaultProtectedRouteProps} outlet={<Dashboard />} />} />
      <Route path='protected' element={<ProtectedRoute {...defaultProtectedRouteProps} outlet={<Protected />} />} />
      <Route path='nested' element={<ProtectedRoute {...defaultProtectedRouteProps} outlet={<Layout />} />}>
        <Route path='one' element={<Protected />} />
        <Route path='two' element={<Protected />} />
      </Route>
      <Route path='login' element={<Login />} />
    </Routes>
  </div>
);

我还更新了 React Router 6 示例。到目前为止,甚至还有关于此的官方指南: https ://reactrouter.com/docs/en/v6/examples/auth

原文由 Robin 发布,翻译遵循 CC BY-SA 4.0 许可协议

您仍然可以使用 SFC 表格,我发现它更简洁一些。只需将您需要的任何道具与 RouteProps

 const PrivateRoute: React.SFC<RouteProps> = ({
  component: Component,
  ...rest
}: {
  component: React.ComponentType<RouteProps>;
}) => (
  <Route
    {...rest}
    render={props =>
      fakeAuth.isAuthenticated
        ? <Component {...props} />
        : <Redirect to="/login" />
    }
  />
);

原文由 Hunter McMillen 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进