简体   繁体   English

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

[英]How to rewrite the protected/private route using TypeScript and React-Router 4, 5 or 6?

I was trying to create a <PrivateRoute> as describe in the react-router documents using TypeScript.我试图创建一个<PrivateRoute> ,如使用 TypeScript 的 react-router 文档中所述。 Can anyone help me out?谁能帮我吗?

The privateRoute in react-router document: react-router 文档中的 privateRoute:

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

Below is my TypeScript version(it won't work) :下面是我的 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 }
                }} />
            )
    )} />
}

The <React.Component {...thisProps} /> is not right. <React.Component {...thisProps} />不正确。 The error is: NodeInvocationException: inst.render is not a function TypeError: inst.render is not a function错误是:NodeInvocationException:inst.render 不是函数 TypeError:inst.render 不是函数

Probably the error has to do with the typing and the implicit return in rendering.错误可能与打字和渲染中的隐式返回有关。 When you fix this you get ultimately to something like this:当你解决这个问题时,你最终会得到这样的结果:

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

This component can be used like this:该组件可以这样使用:

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

There are a few draw backs with the solution above.上述解决方案有一些缺点。 One of the is that you lose type safety.其中之一是您失去了类型安全性。

Probably extending the Route component is the better idea.可能扩展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}/>;
        }
    }
}

So you can use the component like this:所以你可以像这样使用组件:

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

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

Update (Nov 2019)更新(2019 年 11 月)

If you prefer to write functional components you can do it in a very similar manner.如果你更喜欢编写函数式组件,你可以用非常相似的方式来完成。 This also works with React Router 5:这也适用于 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;

Update (Dec 2019)更新(2019 年 12 月)

If you want to redirect a user to the path the user wanted to access first, you need to remember the path, so you can redirect after successful authentication.如果要将用户重定向到用户首先要访问的路径,则需要记住该路径,以便在认证成功后重定向。 The following answer will guide you through that:以下答案将指导您完成:

Redirecting a user to the page they requested after successful authentication with react-router-dom 使用 react-router-dom 成功认证后将用户重定向到他们请求的页面

Update (Mar 2021)更新(2021 年 3 月)

The solution above is a bit outdated.上面的解决方案有点过时了。 The ProtectedRoute component can simply be written as follows: 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 }} />;
  }
};

If you use React Router V6 you need to replace Redirect with Navigate .如果你使用 React Router V6,你需要用Navigate替换Redirect A full example with redirection to the originally requested page can be found here:可以在此处找到重定向到原始请求页面的完整示例:

You can still use the SFC form, which I find a little cleaner.您仍然可以使用 SFC 表单,我觉得它更简洁一些。 Just mix in any props you need with the RouteProps :只需将您需要的任何道具与RouteProps

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

My PrivateRoute我的私人路线

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

Using使用

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

This really helped me这真的帮助了我

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;

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

We can write as below without providing very explicit and exact types or interfaces in tsx.我们可以像下面这样写,而无需在 tsx 中提供非常明确和准确的类型或接口。 Just write like -{ component: Component, ...rest }: any- as type and we are done.只需像 -{ 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>
      );
    }

This is clean and simple.这是干净和简单的。

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;


Seems since react-router-dom 6.0.0-beta.4 for me only that worked:似乎因为 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 https://github.com/remix-run/react-router/issues/8033

For react-router-dom (v6.0.2) , you can use the following code for your PrivateRoute component :对于 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;

To use inside your App.tsx , you can use it as follows:要在您的App.tsx中使用,您可以按如下方式使用它:

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

Just to add what worked for me:只是添加对我有用的内容:

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

and can be used like this:并且可以这样使用:

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

Quick code snippet:快速代码片段:

PrivateRote.tsx PrivateRote.tsx

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

interface PrivateRouteProps extends RouteProps {
  component: any
}

export const PrivateRoute = (props: PrivateRouteProps) => {
  const { component: Component, ...rest } = props
  const { isLogin} = useLogin() //true/false or something else

  return account ? <Route {...rest} render={props => <Component {...props} />} /> : <Redirect to="/" />
}

usage in App.tsx App.tsx 中的用法

<Router>
   <Switch>
      <Route exact path="/" component={Home} />
      <Route exact path="/faq" component={Faq} />
      <PrivateRoute exact path="/profile" component={Profile} />
    </Switch>
</Router>

Using v6 of React-router-dom we handle the protected route in this format使用 React-router-dom 的 v6,我们以这种格式处理受保护的路由<\/h2>

Setting up the Auth protection component设置 Auth 保护组件

import React from "react"; import { Navigate, useLocation, useNavigate } from "react-router-dom"; import { useAppSelector } from "..\/..\/state\/hooks"; const ProtectedRoute: React.FC<{ children: JSX.Element }> = ({ children }) => { const {user} = <Your-State-Provider>\/\/ Redux\/Context or even in-memory user const location = useLocation(); return !user.isAuthenticated ? ( <Navigate to={"\/login"} state={{ from: location }} replace \/> ) : ( children ); }; export default ProtectedRoute;<\/code><\/pre>

In this Basically The user authentication state will be checked then against that condition we user the <Navigate\/><\/code> to redirect back to login page.在此基本上将检查用户身份验证状态,然后根据该条件我们使用<Navigate\/><\/code>重定向回登录页面。 We get the current location and pass it to the Navigate<\/code> so that we redirect the user to the intended page after login automatically.我们获取当前位置并将其传递给Navigate<\/code>以便我们在用户登录后自动将用户重定向到预期页面。 We restructure the children<\/code> props and render the children<\/code> if the user is authenticated.如果用户通过身份验证,我们会重构children<\/code>道具并渲染children<\/code> 。 The advantage of this is that we'll just wrap the element we want to render with the <ProtectedRoute>{children}<\/ProtectedRoute><\/code> .这样做的好处是我们只需用<ProtectedRoute>{children}<\/ProtectedRoute><\/code>包装我们想要渲染的元素。

Consuming the Protected Route使用受保护的路由<\/h3>
import { Fragment } from "react"; import ProtectedRoute from ".\/components\/ProtectedRoute\/ProtectedRoute";\/\/Your protected route import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; import Login from ".\/pages\/Login\/Login"; import MainPage from ".\/pages\/MainPage\/MainPage"; const App = () => { return ( <Router> <Fragment> <nav> <Link to="\/admin" \/> <\/nav> <Routes> <Route path="\/" element={ <ProtectedRoute> <MainPage \/> <\/ProtectedRoute> } \/> <Route path="\/login" element={<Login \/>} \/> <\/Routes> <\/Fragment> <\/Router> ); }; export default App;<\/code><\/pre>

Because react-router-dom v6<\/code> allows nesting of components in the route now we just wrap the component we want to protect with the ProtectedRoute<\/code> eg因为react-router-dom v6<\/code>允许在路由中嵌套组件,所以现在我们只需用ProtectedRoute<\/code>包装我们想要保护的组件,例如

 <Route path="\/" element={ <ProtectedRoute><Your-Protected-page \/><\/ProtectedRoute>}\/><\/code><\/pre>"

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM