<Switch> 组件匹配 React-router-4 中的空值



我正在尝试迁移以使用 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组件检查传递的权限以查看用户是否具有这些权限,如果是,则直接呈现子项,如果没有,则render()返回null

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

根据文档:

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

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

如何为这种情况提供"全部捕获"路由 (404),而无需为 App.js 中许多经过身份验证的路由中的每个路由创建<PrivateRoute>组件?null值真的应该产生匹配吗?

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

<交换机>的所有子元素都应是<路由>或<重定向>元素。

。因此,您的Authorize组件实际上作为Switch的直接子代在那里是不合法的。

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

因此,在您的案例中发生的情况是Switch遍历其子级,直到它到达您的Authorize组件。 然后,它会查看该组件的 props,既找不到path也不是fromprop,并在未定义的路径上调用matchPath。 正如您自己所注意的,"没有路径的<路由>始终匹配",因此matchPath返回 true,并且 Switch 会呈现您的Authorize组件(忽略任何后续的路由或重定向,因为它认为它找到了匹配项)。 但是,Authorize组件中的嵌套"/admin"路由与当前路径不匹配,因此您可以从渲染中返回空结果。

我在工作中也面临类似的情况。 我的解决方法是将路由代码中的 react-routerSwitch替换为自定义组件,该组件遍历其子组件,依次手动呈现每个组件,并返回返回 null 以外的第一个的结果。 当我试一试时,我会更新这个答案。

编辑:嗯,那行不通。 我无法找到一种受支持的方法来手动调用"渲染"在孩子身上。 抱歉,我无法为您提供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函数的组件被假定为来自react-router的有效路由组件,但您可以添加更多条件(可能使用路径属性)来检查它是否是有效的路由

最新更新