我正在尝试迁移以使用React Router 4,并且在<Switch>
组件的逻辑中遇到一些问题,因为它在docs中用于处理404(或不匹配的)路由。 / p>
对于我的条目JavaScript文件,我设置了以下路由。
index.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
组件中,我有更多指定的路由,我确信只有在用户登录后才能访问。
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
的{1}}。
此处的预期行为是,当权限不足且render()
组件呈现时,<Route path="/admin" />
根本无法呈现。
根据docs:
A呈现匹配的第一个孩子。一个 没有路径总是匹配。
但是,如果我转到 <Route component={NotFound} />
组件后声明的任何路由,则路由器与<Authorize>
匹配。这意味着,基于上面的示例,转到null
将返回null。 react-router的预期行为是否返回/users
组件中的第一个匹配项,即使它是<Switch/>
值?
如何为这种情况提供“全能”路由(404),而不为App.js中的许多经过身份验证的路由创建null
组件? <PrivateRoute>
值是否真的产生匹配?
答案 0 :(得分:3)
不幸的是,react-router的Switch
组件不能使用嵌套在其他组件中的路由(如示例中所示)。如果您选中the docs for Switch,则会显示:
&lt; Switch&gt;的所有孩子应该是&lt; Route&gt;或&lt;重定向&gt;元件。
...因此Authorize
组件在Switch
的直接子项中实际上并不合法。
如果您阅读了the source code of the Switch component,那么您会看到它相当不可思议地读取每个孩子的道具并手动应用react-router的matchPath
方法每个孩子的path
(或from
)道具,以确定应该呈现哪一个。
因此,在您的案例中发生的事情是Switch
遍历其子项,直到它到达您的Authorize
组件。然后它会查看该组件的道具,找不到path
或from
道具,并在未定义的路径上调用matchPath
。当你注意到自己时,&#34; a&lt; Route&gt;没有路径总是匹配&#34;,所以matchPath
返回true,并且Switch呈现你的Authorize
组件(忽略任何后续的路由或重定向,因为它认为它找到了匹配)。嵌套的&#39; / admin&#39;然而,Authorize
组件中的路由与当前路径不匹配,因此您从渲染中返回空结果。
我在工作中面临类似的情况。我计划解决这个问题的方法是将路由代码中的react-router Switch
替换为自定义组件,该组件遍历其子项,手动呈现每个子项,并返回第一个结果返回null以外的东西。当我给它一个镜头时,我会更新这个答案。
编辑:嗯,这不起作用。我无法通过支持的方式手动调用&#34;渲染&#34;对孩子们。抱歉,我无法解决Switch
的限制。
答案 1 :(得分:0)
类似的想法罗伯特说,这就是我做的方式
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
的有效Route组件,但是你可以添加更多条件(可能使用路径道具)来检查它是否是有效的Route
答案 2 :(得分:0)
如果有人在> = 2019中阅读了此内容,处理此行为的一种方法是像这样简单地包装Route-component:
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