使用高阶组件进行身份验证时如何延迟检查 redux 存储

Posted

技术标签:

【中文标题】使用高阶组件进行身份验证时如何延迟检查 redux 存储【英文标题】:how to delay checking redux store when authenticating using Higher Order Components 【发布时间】:2016-08-10 01:29:27 【问题描述】:

我正在使用 react、redux、react-router 和更高阶的组件。我有一些用户需要登录才能查看的受保护路线,我已将它们包装在一个更高阶的组件中,我将尝试提供尽可能清晰的上下文,请耐心等待。这是我的高阶组件(HOC):

import React from 'react';
import connect from 'react-redux';
import  browserHistory  from 'react-router';

export function requireAuthentication(Component, redirect) 

class AuthenticatedComponent extends React.Component 

    componentWillMount() 
        this.checkAuth();
    

    componentWillReceiveProps(nextProps) 
        this.checkAuth();
    

    checkAuth() 
        if (!this.props.isAuthenticated) 
            browserHistory.push(redirect);
        
    

    render() 
        console.log('requireAuthentication render');
        return (
            <div>
                this.props.isAuthenticated === true
                    ? <Component ...this.props/>
                    : null
                
            </div>
        )

    


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

return connect(mapStateToProps)(AuthenticatedComponent);


HOC 会检查 redux 存储以查看用户是否已登录,并且它能够执行此操作,因为返回的 Auth 组件已连接到 redux 存储。

这就是我在路由中使用 HOC 与 react 路由器的方式:

import React,  Component  from 'react';
import  Router, Route, browserHistory, IndexRoute  from 'react-router';
import Nav from './nav';
import MainBody from './main-body';
import Login from './login';
import Signup from '../containers/signup';
import Dashboard from './dashboard';
import  requireAuthentication  from './require-authentication';

export default class Everything extends Component 
  render() 
    return (
        <div>
          <a className="iconav-brand intelliagg-logo-container" href="/">
            <span className="iconav-brand-icon intelliagg">Deep-Light</span>
          </a>
          <Router history=browserHistory>
            <Route path='/' component=Login >
            </Route>
            <Route path='/main' component=MainBody>
              <Route path='/dashboard' component= requireAuthentication(Dashboard, '/')  />
              <Route path='/signup' component=Signup />
            </Route>
          </Router>
        </div>
    );
  

我导入 HOC 函数并将受保护的路由传递给 HOC,在本例中为 Dashboard。如果用户未登录并尝试访问此视图,他们将被重定向到登录页面。

当用户启动登录时,操作创建者使用用户提供的用户名和密码向服务器发出请求,我有一个减速器,它使用isAuthenticated: true 的标志更新状态:

import  browserHistory  from 'react-router';

export default function(state = false, action) 
  let newState;
  switch (action.type) 
    case 'LOGGED_IN':
      newState = action.payload.data.login_status;
      return newState;
      break;
    default:
      return state;
  

我的减速器:

import  combineReducers  from 'redux';
import formFields from './reducer_form-fields';
import hintReducer from './reducer_hint-reducer';
import errorMessage from './reducer_error-message-submission';
import loggedIn from './reducer_logged-in';
import isAuthenticated from './reducer_isAuthenticated';

const rootReducer = combineReducers(
  formInput: formFields,
  hint: hintReducer,
  errorMessage,
  loggedIn,
  isAuthenticated
);

export default rootReducer;

所以,我的问题是 HOC 组件总是检查 isAuthenticated 标志,如果设置为 false(这是默认设置),它会将用户重定向到登录页面。由于登录请求需要一些时间并且状态会更新,这意味着用户将始终被重定向到登录页面,因为检查是在客户端立即从 HOC 到 redux 存储的。

如何根据服务器的响应进行检查并相应地重定向用户?有没有办法延迟从 HOC 检查 store 中的 redux 值?

【问题讨论】:

React Router 有一个异步认证流程示例:github.com/reactjs/react-router/blob/master/examples/… 【参考方案1】:

我不同意 Hayo,因为使用 onEnter 是最好的方法。请参阅这篇 SO 帖子,了解为什么我认为 HOC 解决方案(就像您使用的那样)是一种更好的方法:Universal Auth Redux & React Router。

至于您的具体问题,您可以通过将 isAuthenticating 布尔值添加到减速器并在 checkAuth() 函数期间将其传递到您的 HOC 中来完成此操作。如果是这样,您可能希望显示一个替代组件,例如加载栏/微调器/等,以向用户提供一些反馈。你的 reducer 可以默认这个 bool 为 true,然后在 LOGGED_IN 上将它设置为 false。你的 reducer 可能看起来像这样:

const initialState =  isAuthenticating: true, isAuthenticated: false ;
export default function(state = initialState, action) 
  let newState;
  switch (action.type) 
    case 'LOGGED_IN':
      newState =  isAuthenticating: false, isAuthenticated: action.payload.data.login_status ;
      return newState;
    default:
      return state;
  

然后在你的 HOC 的 render() 中,如果 isAuthenticating 为真,你可以返回一个 LoadingComponent 而不是 null。

这与我们刚刚在此 PR 中添加到 redux-auth-wrapper 的功能非常相似 - https://github.com/mjrussell/redux-auth-wrapper/pull/35 如果您想使用它来获得更多参考

【讨论】:

这绝对是另一种解决方案,但是我发现使用 Redux Thunk 实际上会导致 HOC 在 AJAX 请求解析时等待设置标志,而无需使用 spinner 组件。【参考方案2】:

我认为您应该通过使用 react-router 配置动态路由来解决问题。您的解决方案试图解决反应组件中的问题,而不是反应路由器中的问题。

换句话说,如果 isAuthenicated 为假,则转到路由'/'。如果 isAuthenicated 为真,路由将转到仪表板或任何请求的内容。

查看Configuration with Plain Routes 和getComponent fucntions 中的页面末尾。 React-Router 还有一个 onEnter 属性,在您的情况下可能类似于以下内容:

module.exports = store => (
component: require('./main.jsx'),
childRoutes: [
   // public routes
    getComponents(nextState, cb) 
       return require.ensure([], (require) => 
                cb(null, require('./login'))
       )
   ,
   ...
   ,
      onEnter: (nextState, replace) => 
        const  isAuthenticated  = store.getState();
        if (!isAuthenticated) 
           replace('/');
        
     ,
     // Protected routes
     childRoutes: [...  ]
   ,
     // Share the path (magic is here)
     // Dynamically load the correct component
     path: '/',
     getComponent: (location, cb) => 
          const  isAuthenticated  = store.getState();
          if (isAuthenticated) 
              return require.ensure([], (require) => 
                       cb(null, require('./dashboard'))
              )
          
          return require.ensure([], (require) => 
              cb(null, require('./signup'))
          )
      
      

我希望这会有所帮助并给出解决问题的想法。

【讨论】:

以上是关于使用高阶组件进行身份验证时如何延迟检查 redux 存储的主要内容,如果未能解决你的问题,请参考以下文章

酶测试认证高阶组件(HOC)

在 Next.js 中创建一个 HOC(高阶组件)进行身份验证

在 Next.js 中创建一个 HOC(高阶组件)进行身份验证

如何在通过 Redux 操作进行身份验证后重定向用户?

使用 React Redux Redux-Thunk 进行 Firebase 身份验证

react 高阶组件之小学版