<Switch> 组件在 react-router-4 中匹配空值

IT技术 javascript reactjs react-router
2021-05-15 23:36:09

我正在尝试迁移以使用 React Router 4,但在理解<Switch>组件的逻辑时遇到了一些麻烦,因为它在文档中用于处理 404(或不匹配的)路由。

对于我的入口 JavaScript 文件,我设置了以下路由。

索引.js

<Switch>
    <Route path="/login" component={Login} />
    <Route path="/forgot-password" component={ForgotPassword} />
    <Route path="/email-verification" component={EmailVerification} />
    <Route component={App} />
</Switch>

Login组件将检查用户是否已通过身份验证,如果是,则将用户重定向到/dashboard路由(通过history.replace)。

App组件仅在用户通过身份验证时才可访问,并且如果用户未通过身份验证,它会进行类似的检查以将用户重定向到/login

在我的App组件中,我有更多指定的路由,我可以确定只有在用户登录时才能访问这些路由。

应用程序.js

<Switch>
    <Route path="/dashboard" component={Dashboard} />
    <Route path="/accounts" component={Account} />
    <Authorize permissions={['view-admin']}>
        <Route path="/admin" component={Admin} />
    </Authorize>
    <Route path="/users" component={Users} />
    <Route component={NotFound} />
</Switch>

这就是我的问题Authorize组件检查传递的权限以查看用户是否具有这些权限,如果是,则直接呈现子项,如果没有null则从返回render()

这里的预期行为是<Route path="/admin" />当权限不足并且<Route component={NotFound} />组件呈现时根本不呈现。

根据文档

A 呈现匹配的第一个孩子。A 没有路径总是匹配。

但是,如果我转到组件之后声明的任何路由<Authorize>,路由器将匹配null. 这意味着,根据上面的示例,将/users返回 null。react-router 的预期行为是否会返回<Switch/>组件中的第一个匹配项,即使它是一个null值?

如何在不<PrivateRoute>为 App.js 中众多经过身份验证的路由中的每一个创建组件的情况下,为这种情况提供“全能”路由 (404) 一个null值真的应该产生匹配吗?

3个回答

不幸的是,react-router 的Switch组件不适用于嵌套在您的示例中的其他组件中的路由。如果您查看Switch 的文档,它会说:

<Switch> 的所有子元素都应该是 <Route> 或 <Redirect> 元素。

...所以您的Authorize组件作为Switch.

如果您通读了 Switch 组件的源代码,您会发现它相当邪恶地读取每个子组件的 props 并手动将 react-router 的matchPath方法应用于每个子组件的path(or from) prop 以确定应该使用哪个组件呈现。

所以,在你的情况下发生的事情是Switch遍历它的孩子,直到它到达你的Authorize组件。然后查看该组件的 props,没有找到 apathfromprop,并调用matchPath未定义的路径。正如您自己指出的那样,“没有路径的 <Route> 总是匹配”,因此matchPath返回 true,并且 Switch 呈现您的Authorize组件(忽略任何后续的路由或重定向,因为它认为它找到了匹配项)。Authorize但是,组件中嵌套的“/admin”路由与当前路径不匹配,因此您会从渲染中获得空结果。

我在工作中也面临着类似的情况。我解决这个问题的计划是Switch用一个自定义组件替换我的路由代码中的react-router ,该组件遍历其子组件,依次手动渲染每个组件,并返回第一个返回非空值的结果。当我试一试时,我会更新这个答案。

编辑:嗯,那没有用。我无法找到一种受支持的方法来手动调用孩子的“渲染”。抱歉,我无法为您提供解决Switch的限制的方法。

万一有人在 >= 2019 中读到这个,处理这种行为的一种方法是简单地包装 Route 组件,如下所示:

import React from 'react'
import { Route } from 'react-router-dom'

type Props = {
  permissions: string[]
  componentWhenNotAuthorized?: React.ElementType
}

const AuthorizedRoute: React.FunctionComponent<Props> = ({
  permissions,
  componentWhenNotAuthorized: ComponentWhenNotAuthorized,
  ...rest
}) => {
  const isAuthorized = someFancyAuthorizationLogic(permissions)

  return isAuthorized
    ? <Route {...rest} />
    : ComponentWhenNotAuthorized
      ? <ComponentWhenNotAuthorized {...rest} />
      : null
}

export default AuthorizedRoute

然后,简单地使用它:

import React from 'react'
import { Route, Switch } from 'react-router-dom'
import AuthorizedRoute from 'some/path/AuthorizedRoute'
import Account from 'some/path/Account'
import Admin from 'some/path/Admin'
import Dashboard from 'some/path/Dashboard'
import NotFound from 'some/path/NotFound'
import Users from 'some/path/Users'

const AppRouter: React.FunctionComponent = () => (
  <Switch>
    <Route
      component={Account}
      path='/accounts'
    />
    <AuthorizedRoute
      component={Admin}
      componentWhenNotAuthorized={NotFound}
      path='/admin'
      permissions={['view-admin']}
    />
    <Route
      component={Dashboard}
      path='/dashboard'
    />
    <Route
      component={Users}
      path='/users'
    />
    <Route
      component={NotFound}
    />
  </Switch>
)

export default AppRouter

与罗伯特所说的相似,这就是我是如何做到的

class NullComponent extends React.Component {
  shouldComponentBeRenderedByRoute() {
    return false;
  }

  render() {
    return null;
  }
}

class CustomSwitch extends React.Component {
  render() {
    return (
      // React.Children.map returns components even for null, which 
      const children = React.Children.toArray(this.props.children).map(child => {
        const { render, shouldComponentBeRenderedByRoute } = child.type.prototype;
        if (shouldComponentBeRenderedByRoute && !shouldComponentBeRenderedByRoute.call(child)) {
         return null;
        }

        if (shouldComponentBeRenderedByRoute) {
          return render.call(child);
        }

        return child;
      });

      return <Switch>{children}</Switch>;
    );
  }
}

然后使用它只是做

<CustomSwitch>
  <Route path... />
  <NullComponent />
  <Route path... />
</CustomSwitch>

在这里,一个没有shouldComponentBeRenderedByRoute功能的组件被假定为一个有效的 Route 组件react-router,但您可以添加更多条件(可能使用路径props)来检查它是否是一个有效的 Route