【问题标题】:How to rewrite the protected/private route using TypeScript and React-Router 4, 5 or 6?如何使用 TypeScript 和 React-Router 4、5 或 6 重写受保护/私有路由?
【发布时间】:2018-05-24 16:05:53
【问题描述】:

我试图使用 TypeScript 创建一个 <PrivateRoute>,如 react-router documents 中所述。谁能帮帮我?

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 }
                }} />
            )
    )} />
}

&lt;React.Component {...thisProps} /&gt; 不对。错误是:NodeInvocationException:inst.render 不是函数 TypeError: inst.render 不是函数

【问题讨论】:

    标签: reactjs typescript react-router react-router-dom typescript2.0


    【解决方案1】:

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

    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 月)

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

    Redirecting a user to the page they requested after successful authentication with 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。可以在此处找到重定向到最初请求页面的完整示例:

    【讨论】:

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

    您仍然可以使用 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" />
        }
      />
    );
    

    【讨论】:

    • component 应该输入 React.ComponentType&lt;RouteComponentProps&lt;any&gt;&gt; 而不是 React.ComponentType&lt;RouteProps&gt;,不是吗?
    【解决方案3】:

    我的私人路线

    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>
    

    【讨论】:

      【解决方案4】:

      这对我很有帮助

      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

      【讨论】:

        【解决方案5】:

        对于 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>
        

        【讨论】:

          【解决方案6】:

          我们可以如下编写,而无需在 tsx 中提供非常明确和精确的类型或接口。只需像 -{ component: Component, ...rest }: any- 这样写就可以了。

            export default function PrivateRoute({ component: Component, ...rest }: any) {
                const { currentUser } = useAuth();
          
                return (
                  <Route
                    {...rest}
                    render={(props) => {
                      return currentUser ? (
                        <Component {...props} />
                      ) : (
                        <Redirect to="/login" />
                      );
                    }}
                  ></Route>
                );
              }
          

          【讨论】:

            【解决方案7】:

            只是添加对我有用的内容:

            interface PrivateRouteProps extends RouteProps {
              component: React.FC<RouteProps>;
              path: string;
            }
            
            export default function PrivateRoute({
              component: Component,
              path,
            }: PrivateRouteProps) {
              return (
                <Route
                  path={path}
                  render={(props) =>
                    localStorage.getItem('user') ? (
                      <Component {...props} />
                    ) : (
                      <Redirect
                        to={{ pathname: '/login', state: { from: props.location } }}
                      />
                    )
                  }
                />
              );
            }
            

            并且可以这样使用:

            <PrivateRoute path="/user/dashboard" component={Dashboard} />
            

            【讨论】:

              【解决方案8】:

              这是干净和简单的。

              import React from "react";
              import { Route, Redirect, RouteProps } from "react-router-dom";
              
              import { RoutePaths } from "./RoutePaths";
              
              interface Props extends RouteProps {
                  isLoggedIn: boolean;
              }
              
              const AuthRoute: React.FC<Props> = ({ component: Component, ...rest }) => {
                  if (!Component) {
                      return null;
                  }
              
                  const { isLoggedIn } = rest;
              
                  return (
                      <Route
                          {...rest}
                          render={(props) =>
                              isLoggedIn ? (
                                  <Component {...props} />
                              ) : (
                                  <Redirect
                                      to={{
                                          pathname: RoutePaths.Auth,
                                          /**
                                           * For redirecting after login.
                                           */
                                          state: { from: props.location },
                                      }}
                                  />
                              )
                          }
                      />
                  );
              };
              
              export default AuthRoute;
              
              
              

              【讨论】:

                【解决方案9】:

                似乎从 react-router-dom 6.0.0-beta.4 对我来说只有这样的工作:

                App.tsx
                
                import { BrowserRouter as Router, Navigate, Route, Routes } from 'react-router-dom';
                
                interface Props {}
                export const App: React.FC<Props> = ({}) => {
                    const isAuthenticated = true;
                    return (
                        <Router>
                            <Routes>
                                <Route path={`/`} element={isAuthenticated ? <AuthenticatedPage /> : <Navigate to={`/auth`} />} />
                                <Route path={`/auth`} element={<AuthenticationPage />} />
                            </Routes>
                        </Router>
                    );
                };
                
                

                https://github.com/remix-run/react-router/issues/8033

                【讨论】:

                  猜你喜欢
                  • 2022-11-27
                  • 2022-08-08
                  • 2020-10-26
                  • 2018-03-05
                  • 2022-07-30
                  • 1970-01-01
                  • 2018-08-11
                  • 1970-01-01
                  • 2021-02-13
                  相关资源
                  最近更新 更多