从通过 React Router 设置的路由访问 Redux Store
Accessing Redux Store from routes set up via React Router
我想利用 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
来自创建商店的文件,因为在应用程序首次加载时这是未定义的。
完成此操作的最简单方法是将您的商店传递给 return 路由的函数(而不是直接 return 您的路由)。这样你就可以在 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>
);
);
我已经设置了一个示例,您可以在此 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
下的所有路由。您可以在任何地方重复使用它。
随着时间的推移,很多事情都发生了变化。 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>
);
}
我想利用 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
来自创建商店的文件,因为在应用程序首次加载时这是未定义的。
完成此操作的最简单方法是将您的商店传递给 return 路由的函数(而不是直接 return 您的路由)。这样你就可以在 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>
);
);
我已经设置了一个示例,您可以在此 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
下的所有路由。您可以在任何地方重复使用它。
随着时间的推移,很多事情都发生了变化。 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>
);
}