从通过 React Router 设置的路由访问 Redux Store

Posted

技术标签:

【中文标题】从通过 React Router 设置的路由访问 Redux Store【英文标题】:Accessing Redux Store from routes set up via React Router 【发布时间】: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,因为这在应用程序首次加载时是未定义的。

【问题讨论】:

【参考方案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

以上是关于从通过 React Router 设置的路由访问 Redux Store的主要内容,如果未能解决你的问题,请参考以下文章

如何限制对 react-router 中路由的访问?

Redux 中的 React Router 路由参数

如何使用 Typescript 从 react-router-dom 访问路由参数?例如:`/some-route/:slug`

react-router学习笔记

react-router学习笔记

访问 React Router 历史堆栈