在先後端分離的項目中,咱們一般會遇到實現前端路由權限的需求以及全局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;
複製代碼