在先后端分离的项目中,咱们一般会遇到实现前端路由权限的需求以及全局loading效果的需求,在Vue项目中,咱们能够经过路由守卫beforEach、afterEach这个两个钩子函数来实现进入一个路由时的全局loading效果。而vue-router也提供了灵活的路由配置项容许咱们赋予路由更多的信息,包括权限等等。反观react-router并无直接提供给这样的组件。虽说vue-router自己就提供了灵活的配置,可是React高阶组件也赋予了咱们大展身手的机会。前端
const App: React.FC = () => (
<Provider store={store}>
<div className="App">
<Switch>
<AuthRoute config={RouteConfig} />
</Switch>
</div>
</Provider>
);
export default withRouter(App);
复制代码
在最外部咱们不使用react-router提供的Route的组件,而是使用咱们本身封装的路由组件,这个组件接受一个config参数,传入路由配置,这样咱们也能够像vue中那样编写路由配置文件了。vue
定义单个路由配置的类型react
export interface RouteItem {
path: string;
component?: FC;
auth?: boolean;
}
复制代码
最后export出的路由配置信息,就是由RouteItem组成的数组。path表明路由路径,component表示对应的组件,auth表示是否须要鉴权,若是有多种角色的话,那么将auth设置成角色名称,后面增长一下判断方式即可。vue-router
既然要实现全局的loading,那么使用redux最合适不过了。 这里就直接贴代码了,redux的知识就不细说了。 因为使用了combineReducers,全部咱们把loading的状态放在了app这个reducer中。redux
actionTypes.ts后端
const SET_LOADING = 'SET_LOADING';
export default {
/** * 设置页面的loading状态 */
SET_LOADING,
};
复制代码
app.action.ts数组
import actionTypes from './actionTypes';
export const setLoading = (newStatus: boolean) => ({
type: actionTypes.SET_LOADING,
data: newStatus,
});
复制代码
app.reducer.ts网络
import actionTypes from './actionTypes';
export interface AppState {
loading: boolean;
}
const defaultState: AppState = {
loading: false,
};
export default (state = defaultState, action: any) => {
switch (action.type) {
case actionTypes.SET_LOADING:
return { ...state, loading: action.data };
default:
return state;
}
};
复制代码
因为 AuthRoute 组件放在了 Switch 组件内部,React Router 还自动为 AuthRoute 注入了 location 属性,当地址栏的路由发生变化时,就会触发 location 属性对象上的 pathname 属性发生变化,咱们根据这个变化,再去匹配先前写好的路由配置得到相应的组件从新渲染就能够了。react-router
咱们只须要在Route组件的外部包裹一层Spin组件就能够了,spin组件的loading状态就是redux中的loading,若是须要根据网络请求来决定loading时间,只须要在相应的组件里设置loading的值就能够了,为了方便看效果,我这里就直接用定时器了。app
const AuthRoute: React.FC<any> = props => {
const dispatch = useDispatch();
const loading: boolean = useSelector((state: Store) => state.app.loading);
const { pathname } = props.location;
const isLogin = localStorage.getItem('user_token');
let timer = 0;
useEffect(() => {
window.scrollTo(0, 0);
dispatch(setLoading(true));
clearTimeout(timer);
timer = window.setTimeout(() => {
dispatch(setLoading(false));
}, 1000);
}, [pathname]);
const targetRouterConfig: RouteItem = props.config.find(
(v: RouteItem) => v.path === pathname
);
if (targetRouterConfig && !targetRouterConfig.auth && !isLogin) {
const { component } = targetRouterConfig;
return <Route exact path={pathname} component={component} />;
}
if (isLogin) {
// 若是是登录状态,想要跳转到登录,重定向到主页
if (pathname === '/login') {
return <Redirect to="/" />;
}
// 若是路由合法,就跳转到相应的路由
if (targetRouterConfig) {
return (
<Spin
tip="Loading"
size="large"
spinning={loading}
// indicator={<Icon type="loading" style={{ fontSize: 24 }} spin />}
style={{ maxHeight: 'none' }}
>
<Route path={pathname} component={targetRouterConfig.component} />
</Spin>
);
}
// 若是路由不合法,重定向到 404 页面
return <Redirect to="/404" />;
}
// 非登录状态下,当路由合法时且须要权限校验时,跳转到登录页面,要求登录
if (targetRouterConfig && targetRouterConfig.auth) {
return <Redirect to="/login" />;
}
// 非登录状态下,路由不合法时,重定向至 404
return <Redirect to="/404" />;
};
export default AuthRoute;
复制代码