如何在 react redux 中添加受保护的路由

Posted

技术标签:

【中文标题】如何在 react redux 中添加受保护的路由【英文标题】:How to add protected routes in react redux 【发布时间】:2021-05-18 05:23:10 【问题描述】:

我创建了一个登录组件,其中包含所有逻辑内容。

登录reducer是:

const openState = 
  loggedIn: null,
  user: null


export default (state = openState, action) => 
  switch (action.type) 
    case LOGIN:
      return  ...state, loggedIn: true, user: action.payload 
    case LOGOUT:
      return  ...state, loggedIn: false, user: null 
    default:
      return openState
  

行动:

export const logIn = (user) => 
  return 
    type: LOGIN,
    payload: user
  


export const logOut = () => 
  return 
    type: LOGOUT
  

一切正常,但我不确定如何将 loggedInuser 道具从操作传递到路由组件以保护所有路由:

const MainRoutes = props => 
  const  loggedIn  = props;

  console.log(props.loggedIn)

return (
  <Router history=history>
    <Baseline />
    <Menu/>
    <Container maxWidth="md">
      <Switch>
        <Route exact path="/Login" component=Login />
        <Route exact path="/Carousel" component=Carousel />
        <Route exact path="/Stepper" component=Stepper />
        <Route component=NotFound />
      </Switch>
    </Container>
  </Router>
);


const mapStateToProps = (state) => 
return  loggedIn: state.loggedIn ;
;

export default connect(mapStateToProps)(MainRoutes);

如果我控制台记录loggedIn 道具,我会得到未定义的:| 基于loggedIn,我可以在路由组件中创建逻辑。

【问题讨论】:

您在mapStateToProps 内有控制台state.loggedIn 吗?它返回什么? @sayalok 返回未定义,但 state.auth. loggedIn 返回默认状态 null。在登录组件中为 true,但在任何其他组件中返回 null 您是否将操作导入到主文件中,例如 app.js @sayalok 不,不是因为我没有在 app.js 中使用它们 如果您不导入您的操作,您将如何找到loggedIn 【参考方案1】:

这种方式使用react router官方的例子,基本上你需要把你的redux store和这个ProtectedRoute组件连接起来

import  connect  from "react-redux";
import  Route, Redirect  from "react-router-dom";

function PrivateRoute( children, loggedIn, ...rest ) 
  return (
    <Route
      ...rest
      render=( location ) =>
        loggedIn ? (
          children
        ) : (
          <Redirect
            to=
              pathname: "/login",
              state:  from: location 
            
          />
        )
      
    />
  );


const mapStateToProps = (state) => 
  return  loggedIn: state.auth.loggedIn ;
;

export default connect(mapStateToProps)(PrivateRoute);

如您所见,您正在将 loggedIn 值传递给这个新的路由组件,所以如果它为 false,它会将您返回到登录页面。

要使用它,只需调用路由器中的组件:

import PrivateRoute from "./PrivateRoute";

const MainRoutes = () => 

return (
  <Router history=history>
    <Baseline />
    <Menu/>
    <Container maxWidth="md">
      <Switch>
        <Route exact path="/Login" component=Login />
        <PrivateRoute exact path="/Carousel" component=Carousel />
        <PrivateRoute exact path="/Stepper" component=Stepper />
        <Route component=NotFound />
      </Switch>
    </Container>
  </Router>
);

这是一个很好的方法,因为您没有将 MainRoutes 组件连接到商店,在我看来,这不是您在该组件中需要的东西,您最终会在 Routes 内的组件中使用它。

另外,您忘记将 state.auth.loggedIn 添加到您的 Main Routes mapStateToProps,这就是您尝试使用它时未定义的原因。

这是我用来说明该过程的一个小沙盒: https://codesandbox.io/s/unruffled-keller-pwlib

【讨论】:

【参考方案2】:

您可以简单地定义一个自定义的ProtectedRoute 组件,该组件将连接到 redux 状态。在您的情况下,它应该将 state.auth.loggedInstate.auth.user 映射到道具并执行 Redirect 如果这些值是虚假的:

import React from "react";
import  Route, Redirect  from "react-router-dom";
import PropTypes from "prop-types";


const ProtectedRoute = (props) => 
  const  redirectPath, component, user, loggedIn, ...routeProps = props;
  const Component = component;
  const isAccessible = Boolean(user) && loggedIn;

  return (
    <Route
      ...routeProps
      render=props => 
        if (isAccessible) return <Component ...props />;
        return <Redirect to= pathname: redirectPath || "/Login"  />;
      
    />
  );
;

ProtectedRoute.propTypes = 
  path: PropTypes.string.isRequired,
  redirectPath: PropTypes.string,
  component: PropTypes.oneOfType([
    PropTypes.shape( render: PropTypes.func.isRequired ),
    PropTypes.func
  ]),
;

const mapStateToProps = (state) => 
  return  
    loggedIn: state.auth.loggedIn, 
    user: state.auth.user 
  ;
;

export default connect(mapStateToProps)(ProtectedRoute);

有了这个,您的MainRoutes 将不再需要connect

const MainRoutes = props => 
  return (
    <Router history=history>
      ...
      <Container maxWidth="md">
        <Switch>
          <Route exact path="/Login" component=Login />
          <ProtectedRoute exact path="/Carousel" component=Carousel />
          <ProtectedRoute exact path="/Stepper" component=Stepper />
          <Route component=NotFound />
        </Switch>
      </Container>
    </Router>
  );


export default MainRoutes;

更新:

如果你想在页面刷新后保持auth 状态,你需要对你的 redux 存储进行一些额外的设置。主要思想是订阅每个操作,并将新的状态副本放入localStoragecookies,并在您的应用启动之前从选定的存储中持久保存initialState。这是一个例子:

function getFromStorage(key) 
  try 
    return JSON.parse(window.localStorage.getItem(key)) || ;
   catch (err) 
    return ;
  


const initialState = getFromStorage("APP_STATE");

const store = createStore(
  rootReducer,
  initialState, // should go after root reducer
  ...
);

store.subscribe(() => 
  window.localStorage.setItem("APP_STATE", JSON.stringify(store.getState()));
);

这是工作的sandbox(由SuleymanSah 引导)

【讨论】:

使用 render 属性而不是 component 对性能来说要差得多。如果使用此方法,请务必实现类似memo 的性能优化。 @Slbox 你有这个来源吗?我不明白为什么会有显着差异。 @deckele medium.com/@migcoder/… @Slbox 感谢来源。但是,在您提供的文章中,作者声称Route组件的componentrender prop在性能上没有区别。 我想我应该读过结论。 Here's a Stack answer on the topic.【参考方案3】:

您可以创建一个 Guard,它从 Redux 存储获取登录状态并在登录时呈现子级,否则重定向到其他地方。 要从 store 中获取 state,可以使用 useSelectorReact Hook。

import  useSelector  from 'react-redux';

function AuthGuard(children) 
    const loggedIn = useSelector(store => store.loggedIn);
    
    if (loggedIn) 
        return children;  
     else 
        return <Redirect to="/login" />
    

在路上

    <Container maxWidth="md">
      <Switch>
        <Route exact path="/Login" component=Login />
        <AuthGuard>
            <Route exact path="/Carousel" component=Carousel />
            <Route exact path="/Stepper" component=Stepper />
        </AuthGuard>
        <Route component=NotFound />
      </Switch>
    </Container>

【讨论】:

【参考方案4】:

正如其他人已经描述的那样,您最好创建一条受保护的路线来完成您想要的。如果他/她没有登录,您只需将用户重定向到登录路由。

这是我的实现:(codesandbox)

import React from "react";
import  Route, Redirect  from "react-router-dom";
import  connect  from "react-redux";

const ProtectedRoute = (
  path,
  component: Component,
  render,
  loggedIn,
  ...rest
) => 
  return (
    <Route
      path=path
      ...rest
      render=(props) => 
        if (loggedIn) 
          return Component ? <Component ...props /> : render(props);
        
        return (
          <Redirect
            to=
              pathname: "/login",
              state:  from: props.location 
            
          />
        );
      
    />
  );
;

const mapStateToProps = (state) => 
  const  loggedIn  = state.auth;
  return 
    loggedIn
  ;
;

export default connect(mapStateToProps)(ProtectedRoute);

【讨论】:

问题是我已经使用了你的例子,但并没有真正理解为什么在浏览器刷新时路由变得未知,不知道为什么:| @RulerNature redux 状态在浏览器刷新时丢失,这是正常的。你可以查看 redux-persist 包。 github.com/rt2zz/redux-persist【参考方案5】:

下面的代码对我有用。

const SecretRouter = ( component: Component, location, loggedIn , ...rest ) => (
  <Route
    location=location
    ...rest
    render=(props) =>
      loggedIn === true ? (
        <Component ...props />
      ) : (
          <Redirect
            to=
              pathname: "/Login"
            
          />
      )
    
  />
);

const mapStateToProps = (state) => 
return  loggedIn: state.loggedIn ;
;

export const PrivateRouter = connect(mapStateToProps)(
  SecretRouter
);

const MainRoutes = props => 

return (
  <Router history=history>
    <Baseline />
    <Menu/>
    <Container maxWidth="md">
      <Switch>
        <Route exact path="/Login" component=Login />
        <PrivateRouter exact path="/Carousel" component=Carousel />
        <PrivateRouter exact path="/Stepper" component=Stepper />
        <Route component=NotFound />
      </Switch>
    </Container>
  </Router>
);



export default MainRoutes;

【讨论】:

【参考方案6】:

在这种情况下我要做的基本上是创建一个私有路由,只有在您登录时才能访问,或者在您的情况下,如果您的 loggedIn: state.loggedIn 中的登录为 true 并且用户存在于state.auth.user。如果两者都是错误的,它会将您重定向到 /login 页面以在访问这些私有路由之前先登录或注册。

所以要创建一个私有路由或&lt;PrivateRoute /&gt;

import React from 'react';
import  Route, Redirect  from 'react-router-dom';
import PropTypes from 'prop-types';
import  connect  from 'react-redux';


const PrivateRoute = ( 
  component: Component,
  auth:  user, loggedIn ,
  ...rest 
) => (
  <Route 
    ...rest 
    render=props => 
      loggedIn && Boolean(user) ? (
        <Component ...props />  
      ) : (
        <Redirect to="/login" />
      )
     
  />
);

PrivateRoute.propTypes = 
  auth: PropTypes.object.isRequired


const mapStateToProps = state => (
  auth: state.auth
);

export default connect(mapStateToProps)(PrivateRoute);

这将为您创建该私有路由或&lt;PrivateRoute /&gt;,您可以将其导入您的MainRoutes 文件,然后您可以将应该受私有路由保护的组件传递给它。

const MainRoutes = props => 
  return (
    <Router history=history>
      <Baseline />
      <Menu/>
      <Container maxWidth="md">
        <Switch>
          <Route exact path="/Login" component=Login />
          <PrivateRoute exact path="/Carousel" component=Carousel />
          <PrivateRoute exact path="/Stepper" component=Stepper />
          <Route component=NotFound />
        </Switch>
      </Container>
    </Router>
  );


export default MainRoutes;

这样,要保护的组件将检查用户是否存在以及您是否已登录,如果为真,它将允许您访问这些组件。

【讨论】:

以上是关于如何在 react redux 中添加受保护的路由的主要内容,如果未能解决你的问题,请参考以下文章

即使我在 react/redux 中使用受保护的路由设置了身份验证,我仍然会被注销?当我刷新页面时

受保护的路由 React Router 4 无法使用存储在 Redux 中的身份验证状态

在反应中使用 cookie 的客户端身份验证和受保护的路由

React Router 受保护的路由

使用React构建受保护的路由

React-router-dom 受保护的路由不起作用