我正在尝试迁移以使用 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
也不是from
prop,并在未定义的路径上调用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
的有效路由组件,但您可以添加更多条件(可能使用路径属性)来检查它是否是有效的路由