从通过 React 路由器设置的路由访问 Redux 存储

Accessing Redux Store from routes set up via React Router

本文关键字:路由 访问 Redux 存储 设置 React 路由器      更新时间:2023-09-26

我想利用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,因为这在应用程序首次加载时是未定义的。

实现此目的的最简单方法是将存储传递给返回路由的函数(而不是直接返回路由)。这样,您就可以在onEnter和其他反应路由器方法中访问存储。

因此,对于您的路线:

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 函数,在存储中传递:

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

希望这有帮助。

如果你

愿意,你可以写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>
    );
);

我已经设置了一个示例,您可以在此代码笔中使用。

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

我没有使用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下的所有路由。您可以在任何您喜欢的地方重复使用它。

随着时间的推移,很多事情都发生了变化。 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>
  )
}

在尝试了上述一些建议之后,我发现通过更新跟踪商店状态的最佳方法是使用 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>
    );
}