我可以在React Router中的交换机内使用高阶私有路由组件吗?

时间:2019-06-28 17:35:08

标签: javascript reactjs react-router-dom

我正在尝试建立一个Router来使用自定义PrivateRoute组件,这些组件与官方react-router文档中给出的example等效。问题是我还想使用全部捕获的404路由,所以我认为我也需要使用Switch组件,如文档中的this example所示。

这里存在冲突,因为Switch的{​​{3}}声明Switch组件的子代必须是Route组件,而在docs中,小组指出,不支持在Switch内使用非路由组件,即使它确实可行。

我已经创建了高阶路由,目前正在Switch中使用它们,并且似乎可以正常工作,但这无法通过我公司的代码审查,因为它使用的是不受支持的API,随时休息。我想知道是否存在一种完全支持的方式来使用高阶路由和全部路由。

我正在研究尝试创建404路由而不需要使用Switch,或者使用常规的Route组件,但是在身份验证中包装了传递给Route的组件逻辑。

import { Router, Route, Switch } from 'react-router-dom';

// Array of authorization functions to be tested by private routes
const authCriteria = [
  // The next route must have been referred by a previous route
  props => defined(props.location.state.from),
];

// Seems to work as expected, but violates the supported use 
// of the Switch component
const AppRouter = () => {
  return (
    <Router>
      <Switch>
        <Route exact path='/' component={Store} />
        <PrivateRoute 
          exact
          path='/account' 
          component={Account}
          authCriteria={authCriteria}
        />
        <PrivateRoute 
          exact
          path='/shipping' 
          component={Shipping} 
          authCriteria={authCriteria}
        />
        <PrivateRoute 
          exact 
          path='/checkout' 
          component={Checkout} 
          authCriteria={authCriteria}
        />
        // Catch-all route means we need to use a Switch
        <Route render={() => (<h2>Not Found</h2>)} />
      </Switch>
    </Router>
  );
};

export default AppRouter;

2 个答案:

答案 0 :(得分:2)

根据您问题的链接到react-router的github的链接,看来您可以在Switch内使用HOC。

但是,您可以实现类似的目标。基本上,您需要两个路由器。第一个Router会路由所有预先验证的路由,例如登录,新用户,忘记密码等。由于它位于Switch中,因此它应该呈现您的第二个Router。您的第二个Router将包含您所有需要身份验证的路由。但是,您需要包括条件检查以确保用户已通过身份验证。如果用户未通过身份验证,则只需将Redirect返回到/login路径即可。这是因为第一个Switch有效。如果用户使用未经身份验证的用户无法访问的网址,则该用户将始终被重定向到/login,因为他们将在第一开关和第二开关的Redirect中达到默认条件组件。

import { Router, Route, Switch } from 'react-router-dom';

const AppRouter = (props) => {
  return (
    <Router>
      <Switch>
        <Route exact path='/login' component={Login} />
        <Route exact path='/new-user' component={CreateAccount} />
        <Route render={props => <AuthenticationRouter {...props} />}
      </Switch>
    </Router>
  );
};

const AuthenticatedRouter = (props) => {
  if( props.userIsAuthenticated ) {
    return (
      <Router>
        <Switch>
          <Route exact path='/' component={Store} />
          <Route 
            exact
            path='/account' 
            component={Account}
          />
          <Route 
            exact
            path='/shipping' 
            component={Shipping} 
          />
          <Route 
            exact 
            path='/checkout' 
            component={Checkout} 
          />
          <Route render={() => (<h2>Not Found</h2>)} />
        </Switch>
      </Router>
    );
  }

  return (
    <Redirect
      to={{
        pathname: "/login",
        state: { from: props.location }
      }}
    />
  );
};

export default AppRouter;

答案 1 :(得分:1)

更新: 我更改了路由身份验证HoC,以包装路由呈现的组件,而不是路由本身。如果身份验证条件是静态的,则发布的第一个答案可能会更好,但是我正在使用的应用是下订单工作流,其中访问不同路线可能取决于不同的因素,例如完成或来自其他路线,因此我需要能够将任意身份验证标准传递给每个路由。

以下代码显示了我用来包装每个组件的withAuth HoC以及它如何与路由器一起工作:

// Test every passed-in auth verification function.
const verifyAuth = (authCriteria, props) => {
  if (authCriteria.length === 0) return true;
  return authCriteria.every(criterion => criterion(props));
};

// Authentication HoC
const withAuth = ({
  authCriteria = [],
  redirectPath = '/',
} = {}) => Component => props => {
  const isAuthorized = verifyAuth(authCriteria, props);
  return (
    isAuthorized ? (
      <Component {...props} />
    ) : (
      <Redirect 
        to={{ 
          pathname: redirectPath, 
          state: { from: props.location },
        }} 
      />
    )
  );
};

// TODO: authenticate user
const validUser = _props => true; 
// The next route must have been referred by a previous route
const internalReferral = props => defined(props.location.state);

// The Store route has different authentication requirements
// than the other two routes 
const storeCriteria = [validUser];
const mainCriteria = [validUser, internalReferral];

const authRoute = withAuth({ authCriteria: mainCriteria });

const ProtectedRoutes = {
  Store: withAuth({ authCriteria: storeCriteria })(Store),
  Shipping: authRoute(Shipping),
  Checkout: authRoute(Checkout),
};

const AppRouter = () => {
  return (
    <Router>
      <Switch>
        <Route exact path='/' component={ProtectedRoutes.Store} />
        <Route exact path='/shipping' component={ProtectedRoutes.Shipping} />
        <Route exact path='/checkout' component={ProtectedRoutes.Checkout} />
        <Route render={() => (<h2>Not Found</h2>)} />
      </Switch>
    </Router>
  );
};