我正在使用 react-router (v2.8.1) 和 ES6 语法 编写 React.js 应用程序 (v15.3)。我无法让路由器代码拦截页面之间的所有转换以检查用户是否需要先登录。
我的顶级渲染方法非常简单(应用程序也很简单):
render()
{
return (
<Router history={hashHistory}>
<Route path="/" component={AppMain}>
<Route path="login" component={Login}/>
<Route path="logout" component={Logout}/>
<Route path="subject" component={SubjectPanel}/>
<Route path="all" component={NotesPanel}/>
</Route>
</Router>
);
}
网络上的所有示例都使用 ES5 代码或旧版本的 React-router(早于版本 2),并且我对 mixins(已弃用)和 willTransitionTo(从未被调用)的各种尝试都失败了。
如何设置全局“拦截器功能”以强制用户在登陆他们请求的页面之前进行身份验证?
每个路由都有一个 onEnter 钩子,它在路由转换发生之前被调用。使用自定义 requireAuth 函数处理 onEnter 挂钩。
<Route path="/search" component={Search} onEnter={requireAuth} />
示例 requireAuth 如下所示。如果用户已通过身份验证,则通过 next() 进行转换。否则将路径名替换为 /login 并通过 next() 进行转换。登录还会传递当前路径名,以便登录完成后,用户将被重定向到最初请求的路径。
function requireAuth(nextState, replace, next) {
if (!authenticated) {
replace({
pathname: "/login",
state: {nextPathname: nextState.location.pathname}
});
}
next();
}
在 v4 中,您只需创建一个路由组件来检查用户是否经过身份验证并返回下一个组件,当然下一个组件可以是其他路由。
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Route, Redirect } from 'react-router-dom';
import AuthMiddleware from 'modules/middlewares/AuthMiddleware';
class PrivateRoute extends Component {
static propTypes = {
component: PropTypes.func.isRequired,
isAuthenticated: PropTypes.bool,
isLoggedIn: PropTypes.func.isRequired,
isError: PropTypes.bool.isRequired
};
static defaultProps = {
isAuthenticated: false
};
constructor(props) {
super(props);
if (!props.isAuthenticated) {
setTimeout(() => {
props.isLoggedIn();
}, 5);
}
}
componentWillMount() {
if (this.props.isAuthenticated) {
console.log('authenticated');
} else {
console.log('not authenticated');
}
}
componentWillUnmount() {}
render() {
const { isAuthenticated, component, isError, ...rest } = this.props;
if (isAuthenticated !== null) {
return (
<Route
{...rest}
render={props => (
isAuthenticated ? (
React.createElement(component, props)
) : (
<Redirect
to={{
pathname: isError ? '/login' : '/welcome',
state: { from: props.location }
}}
/>
)
)}
/>
);
} return null;
}
}
const mapStateToProps = (state) => {
return {
isAuthenticated: state.auth.isAuthenticated,
isError: state.auth.isError
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
isLoggedIn: () => AuthMiddleware.isLoggedIn()
}, dispatch);
};
export default connect(mapStateToProps, mapDispatchToProps)(PrivateRoute);
requireAuth(nextState,
replace)
{
if(!this.authenticated()) // pseudocode - SYNCHRONOUS function (cannot be async without extra callback parameter to this function)
replace('/login')
}
解释 V1 与 v2 之间的反应路由器重定向差异的链接是
Likewise, redirecting from an onEnter hook now also uses a location descriptor.
// v1.0.x
(nextState, replaceState) => replaceState(null, '/foo')
(nextState, replaceState) => replaceState(null, '/foo', { the: 'query' })
// v2.0.0
(nextState, replace) => replace('/foo')
(nextState, replace) => replace({ pathname: '/foo', query: { the: 'query' } })
完整代码列表如下(react-router 版本 2.8.1):
requireAuth(nextState,
replace)
{
if(!this.authenticated()) // pseudocode - SYNCHRONOUS function (cannot be async without extra callback parameter to this function)
replace('/login');
}
render() {
return (
<Router history={hashHistory}>
<Route path="/" component={AppMain}>
<Route path="login" component={Login}/>
<Route path="logout" component={Logout}/>
<Route path="subject" component={SubjectPanel} onEnter={this.requireAuth}/>
<Route path="all" component={NotesPanel} onEnter={this.requireAuth}/>
</Route>
</Router>
);
}
这不是一个安全的解决方案
您可以尝试在每个需要
login的页面上使用useEffect钩子:
useEffect(() => {
const token = localStorage.getItem('token');
if(!token) {
history.push('/login');
}
}
这使用来自“react-router-dom”的 useHistory 钩子
你只需要在调用它之前初始化它:
const history = useHistory();
如上所述,这不是一个安全的解决方案,而是一个简单的解决方案
代码:
const RestrictedRoute = ({ component: Component, authUser, auth, ...rest }) => (
<Route
{...rest}
render={props =>
auth.is_authenticated && auth.is_authorized && authUser ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: '/signin',
state: { from: props.location },
}}
/>
)
}
/>
);
<Switch>
<RestrictedRoute path={`${match.url}app`}
authUser={authUser}
auth={{ is_authenticated, is_authorized }}
component={MainApp}
/>
<Route path='/signin' component={SignIn} />
<Switch>
React v18
和
reacr-router V6
中。下面是我如何做到这一点的完整结构。创建路线
routes.tsx
import { lazy } from 'react';
const Login = lazy(() => import('../pages/Authentication/Login'));
const ProtectedPage = lazy(() => import('../pages/ProtectedPage'));
const PublicPage = lazy(() => import('../pages/PublicPage'));
const routes = [
{
path: "/Login",
element: <Login/>,
protected: false
},
{
path: "/PublicPage",
element: <PublicPage />,
protected: false
},
{
path: "/ProtectedPage",
element: <ProtectedPage />,
protected: true
}
];
export { routes };
创建AuthRequired.tsx
组件来检查登录状态:
import { useSelector } from 'react-redux';
import { Navigate } from 'react-router-dom';
export const AuthRequired: FC<Props> = ({ children }) => {
const { user } = useSelector((state: IRootState) => state);
const isLoggedIn = Object.keys(user).length > 0;
return isLoggedIn ? children : <Navigate to={AppRoutes.LOGIN} replace={true} />;
};
在您的createBrowserRouter
import { createBrowserRouter } from 'react-router-dom';
import { AuthRequired } from './AuthRequired';
import { routes } from './routes';
import App from './App';
const finalRoutes = routes.map((route) => {
return {
...route,
element:
route.protected ? (
<AuthRequired><App>{route.element}</App></AuthRequired>
) : (
<App>{route.element}</App>
),
};
});
const router = createBrowserRouter(finalRoutes);
export default router;
然后在您的 main.tsx
或文件中使用它,无论您在哪里创建根目录。
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<Suspense>
<Provider store={store}>
<RouterProvider router={router} />
</Provider>
</Suspense>
</React.StrictMode>
);