【问题标题】:Accessing Redux Store from routes set up via React Router从通过 React Router 设置的路由访问 Redux Store
【发布时间】:2016-06-21 08:54:15
【问题描述】:

我想利用 react-router 的 onEnter 处理程序来提示用户在进入受限路由时进行身份验证。

到目前为止,我的 routes.js 文件看起来像这样:

import React from 'react';
import { Route, IndexRoute } from 'react-router';

export default (
    <Route   path="/"         component={App}>
      <IndexRoute             component={Landing} />
      <Route path="learn"     component={Learn} />
      <Route path="about"     component={About} />
      <Route path="downloads" component={Downloads} onEnter={requireAuth} />
    </Route>
)

理想情况下,我希望我的 requireAuth 函数是一个可以访问存储和当前状态的 redux 操作,其工作方式如下:store.dispatch(requireAuth())

很遗憾,我无权访问此文件中的商店。在这种情况下,我认为我不能真正使用 connect 来访问我想要的相关操作。我也不能只从创建商店的文件中import store,因为这在应用程序首次加载时是未定义的。

【问题讨论】:

    标签: javascript reactjs react-router redux


    【解决方案1】:

    完成此操作的最简单方法是将您的商店传递给返回您的路线的函数(而不是直接返回您的路线)。这样你就可以在onEnter和其他react路由方法中访问store了。

    所以对于你的路线:

    import React from 'react';
    import { Route, IndexRoute } from 'react-router';
    
    export const getRoutes = (store) => (
      const authRequired = (nextState, replaceState) => {
        // Now you can access the store object here.
        const state = store.getState();
    
        if (!state.user.isAuthenticated) {
          // Not authenticated, redirect to login.
          replaceState({ nextPathname: nextState.location.pathname }, '/login');
        }
      };
    
      return (
        <Route   path="/"         component={App}>
          <IndexRoute             component={Landing} />
          <Route path="learn"     component={Learn} />
          <Route path="about"     component={About} />
          <Route path="downloads" component={Downloads} onEnter={authRequired} />
        </Route>
      );
    )
    

    然后更新你的主组件调用getRoutes函数,传入store:

    <Provider store={ store }>
      <Router history={ history }>
        { getRoutes(store) }
      </Router>
    </Provider>
    

    至于从requireAuth 调度一个动作,你可以这样写你的函数:

    const authRequired = (nextState, replaceState, callback) => {
      store.dispatch(requireAuth())  // Assume this action returns a promise
        .then(() => {
          const state = store.getState();
    
          if (!state.user.isAuthenticated) {
            // Not authenticated, redirect to login.
            replaceState({ nextPathname: nextState.location.pathname }, '/login');
          }
    
          // All ok
          callback();
        });
    };
    

    希望这会有所帮助。

    【讨论】:

    • 这是一个很好的例子。非常感谢:)
    • TY 有点多,这种方法很容易实现,但我想问一下,这样做有什么缺点吗?
    • react-router 有一个小的变化,从 onEnter 挂钩重定向现在也使用位置描述符。见github.com/ReactTraining/react-router/blob/master/…
    【解决方案2】:

    如果你愿意,你可以这样写 route.js:

    var requireAuth = (store, nextState, replace) => {
      console.log("store: ", store);
      //now you have access to the store in the onEnter hook!
    }
    
    export default (store) => {
      return (
          <Route path="/"           component={App}>
            <IndexRoute             component={Landing} />
            <Route path="learn"     component={Learn} />
            <Route path="about"     component={About} />
            <Route path="downloads" component={Downloads} onEnter={requireAuth.bind(this, store)} />
          </Route>
        );
    );
    

    我已经设置了一个示例,您可以在 codepen 中使用它。

    不确定触发操作以处理身份验证是否是个好主意。我个人更喜欢以不同的方式处理身份验证:

    我没有使用onEnter 钩子,而是使用了一个包装函数。我希望我的博客的管理部分受到保护,因此我将 AdminContainer 组件包装在带有函数 requireAuthentication 的路由中,见下文。

    export default (store, history) => {
            return (
                <Router history={history}>
                    <Route path="/" component={App}>
                        { /* Home (main) route */ }
                        <IndexRoute component={HomeContainer}/>
                        <Route path="post/:slug" component={PostPage}/>
                        { /* <Route path="*" component={NotFound} status={404} /> */ }
                    </Route>
    
                    <Route path="/admin" component={requireAuthentication(AdminContainer)}>
                        <IndexRoute component={PostList}/>
                        <Route path=":slug/edit" component={PostEditor}/>
                        <Route path="add" component={PostEditor}/>
                    </Route>
                    <Route path="/login" component={Login}/>
                </Router>
            );
        };
    

    requireAuthentication 是一个函数,

    • 如果用户通过身份验证,则呈现包装的组件,
    • 否则重定向到Login

    你可以在下面看到它:

    export default function requireAuthentication(Component) {
        class AuthenticatedComponent extends React.Component {
    
            componentWillMount () {
                this.checkAuth();
            }
    
            componentWillReceiveProps (nextProps) {
                this.checkAuth();
            }
    
            checkAuth () {
                if (!this.props.isAuthenticated) {
                    let redirectAfterLogin = this.props.location.pathname;
                    this.context.router.replace({pathname: '/login', state: {redirectAfterLogin: redirectAfterLogin}});
                }
            }
    
            render () {
                return (
                    <div>
                        {this.props.isAuthenticated === true
                            ? <Component {...this.props}/>
                            : null
                        }
                    </div>
                )
    
            }
        }
    
        const mapStateToProps = (state) => ({
            isAuthenticated: state.blog.get('isAuthenticated')
        });
    
        AuthenticatedComponent.contextTypes = {
            router: React.PropTypes.object.isRequired
        };
    
        return connect(mapStateToProps)(AuthenticatedComponent);
    }
    

    另外,requireAuthentication 将保护/admin 下的所有路由。而且你可以在任何你喜欢的地方重复使用它。

    【讨论】:

    • 似乎 AuthenticatedComponent 是可视化 React 组件的使用,用于非可视化路由身份验证检查目的。难道你不认为所有这些 componentWillMount 根本不是关于路由身份验证的检查吗?
    • 我同意 @alex_1948511,这是一个 hack。但是话又说回来,在 JS 世界中很少有东西被很好地定义(或者也许这只是我作为 JS 编程中的 n00b 的观点)。我愿意接受有关如何在 React 中更好地做到这一点的任何建议。在过去的几个月里我没有研究过这个,因为我在互联网上的某个地方找到了这种方法,所以我并没有进一步研究。 :-)
    • 我可以补充一点,在路由器 v4 中,您不能嵌套路由器标签。这将引发错误
    【解决方案3】:

    随着时间的推移,很多东西都发生了变化。 onEnter 不再存在于react-router-4

    以下来自我的真实项目,供大家参考

    export const getRoutes = (store) => {
      const PrivateRoute = ({ component: Component, ...rest }) => (
        <Route {...rest} render={props => (
          checkIfAuthed(store) ? (
            <Component {...props}/>
          ) : (
            <Redirect to={{
              pathname: '/login'
            }}/>
          )
        )}/>
      )
    
      return (
        <Router>
          <div>
            <PrivateRoute exact path="/" component={Home}/>
            <Route path="/login" component={Login} />
          </div>
        </Router>
      )
    }

    【讨论】:

      【解决方案4】:

      在尝试了上面的一些建议后,我发现使用更新跟踪存储状态的最佳方法是使用 React-Redux 的 useSelector 函数,该函数基本上将功能组件连接到存储。

      import * as React from "react";
      import {Redirect, Route, Switch} from "react-router";
      import {Provider, useSelector} from "react-redux";
      import { createBrowserHistory } from "history";
      
      // Your imports
      import {IApplicationState,} from "./store/store";
      import {Login} from "./routes/login/login.component";
      import {getToken} from "./store/helpers/httpHelpers";
      
      
      function handleRedirect() {
          if(!getToken()) {
              return <Redirect to="/login"/>;
          }
      }
      
      const restricted = (Component: _ComponentType, isLoggedIn: boolean) => {
         // Don't redirect here if there is a token in localStorage.
         // This is happening when we are on a restricted route and the user
         // refreshes & the isLoggedIn state hasn't been updated yet.
          return !isLoggedIn ? (
              () => handleRedirect()
          ) : () => <Route component={Component}/>
      };
      
      const AuthenticateRoutes = () => {
          const isLoggedIn = useSelector((state: IApplicationState) => state.auth.isLoggedIn);
          return (
              <Switch>
                  <Route path="/login" component={Login} />
                  <Route path="/downloads" render={restricted(Download, isLoggedIn)} />
              </Switch>
          );
      };
      
      export function App() {
          return (
              <Provider store={store}>
                  <>
                      <Router history={createBrowserHistory()}>
                          <AuthenticateRoutes />
                      </Router>
                  </>
              </Provider>
          );
      }
      

      【讨论】:

      • 请添加导入,尤其是在 TypeScript 上下文中。
      • @BairDev 导入已添加,如果您需要任何其他信息,请告诉我。
      • 是的,愚蠢的问题:history 是什么?它看起来像一个 npm 包。
      • @BairDev 在这里解释:github.com/ReactTraining/history
      猜你喜欢
      • 2018-10-03
      • 2021-01-09
      • 2018-12-27
      • 2015-08-03
      • 2016-05-12
      • 1970-01-01
      • 2016-09-02
      • 2020-08-13
      • 2023-03-12
      相关资源
      最近更新 更多