React 路由守衛和全局loading控制

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

既然要實現全局的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組件

因爲 AuthRoute 組件放在了 Switch 組件內部,React Router 還自動爲 AuthRoute 注入了 location 屬性,當地址欄的路由發生變化時,就會觸發 location 屬性對象上的 pathname 屬性發生變化,咱們根據這個變化,再去匹配先前寫好的路由配置得到相應的組件從新渲染就能夠了。react-router

實現全局loading

咱們只須要在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;
複製代碼

參考文章

相關文章
相關標籤/搜索