四、使用重定向和切换组件

使用 React 路由的<Redirect>组件可以实现用户从一条路由到另一条路由的重定向。在传统网站中,页面在服务器端呈现,托管应用的 web 服务器配置了重写规则,将用户重定向到不同的 URL。当内容移动到新页面时,以及站点的某些页面仍在构建中时,可以使用此重定向。HTTP 重定向是一项昂贵的操作,因此应用的性能也会受到影响。

单页应用SPA中)中,重定向发生在浏览器上,根据特定条件将用户重定向到不同的路由。这种重定向速度更快,因为不涉及 HTTP 往返,转换类似于使用<Link><NavLink>组件从一条路由导航到另一条路由。

本章讨论了以下主题:

  • <Redirect>组件:将用户从一条路由重定向到另一条路由
  • 保护路由和授权:当试图访问受保护的路由时,用户被重定向到登录页面的用例
  • <Switch>组件:呈现第一个匹配<Route>
  • 添加 404 页面未找到页面:当<Route>组件与浏览器的 URL 路径不匹配时,使用<Switch><Route><Switch><Redirect>组件呈现 404 页面的用例

组件

<Redirect>部件包含在react-router-dom包装中。它有助于将用户从包含它的组件重定向到'to'道具中指定的路由:

import { Redirect } from 'react-router-dom';

export class HomeComponent extends Component {
    render() {
        return (
            <Redirect to='/dashboard' />
        )
    }
}

在前面的场景中,当呈现HomeComponent时(基于<Route>匹配),用户被重定向到'/dashboard'路由。例如,当用户访问主页(路径'/'时,路径'/'<Route>呈现上一个组件,然后用户立即重定向到路径值为'/dashboard'<Route>。这类似于使用带有'to'道具的<Link><NavLink>组件将用户导航到不同路由。在这里,当呈现组件时,将发生重定向,而不是作为用户操作的结果触发导航。

前面提到的重定向示例非常适合应用中的某些页面移动到不同目录的情况

<Redirect>组件与 React 路由中的其他组件类似,如<Route><Link>。如前所述,它是可以包含在渲染函数中的 React 组件。此外,<Redirect>组件接受一组与<Link>组件类似的道具。

支撑

to 属性用于指定用户应重定向到的路由。如果找到匹配的<Route>,则将用户重定向到指定的路径,并呈现相应的组件

to 道具还可以接受一个对象,该对象指定了pathnamesearchhashstate属性的值:

<Redirect 
    to={{
        pathname: '/dashboard',
        search: '?q=1',
        hash: '#hash',
        state: { from: match.url }
      }} 
/>

<Link>组件类似,前面提到的属性在<Redirect>组件的 to 属性中指定。请注意,state 属性的值为{ from: match.url }。此处,match.url提供浏览器 URL 路径的当前值,然后在重定向发生时将该值提供给呈现组件

然后,呈现的组件可以使用this.props.location.state读取状态信息:

export class DashboardComponent extends Component {
    render() {
        const { location } = this.props;
        return (
            <div>
                In DashboardComponent <br />
                From : {location.state.from}
            </div>
        )
    }
}

在前面的示例中,DashboardComponent作为来自HomeComponent的重定向的结果而呈现。location.state.from的值将发生重定向的页面的路径信息共享给重定向组件。当您有一个要重定向到的通用页面,并且重定向页面必须显示有关发生重定向的路径的信息时,这非常有用。例如,当应用中发生错误时,应将用户重定向到呈现错误消息的页面,提供错误发生页面上的信息。在这种情况下,状态信息可以包括属性-errorMessagefrom;后者的值为match.url,即发生错误的页面。

If the redirected <Route> is not found, the browser's URL is updated and no errors are thrown. This is by design; ideally, if there is no matching route, the user should be redirected to a 404 or a Page Not Found page. The <Route> to render when there's no match is discussed in the next section.

在组件内部,当您尝试重定向到同一路由时,React Router 会抛出一条警告消息警告:您尝试重定向到当前所在的同一路由:"/home"。此检查确保重定向不会导致无限循环

也可能会遇到这样的情况,重定向组件在其渲染方法中包含一个<Redirect>,重定向回相同的路由,即遵循此路由重定向path: /home => /dashboard => /home。这将运行到一个循环中,直到 React 停止渲染组件;React 然后抛出一个超过最大更新深度的错误。当组件在componentWillUpdatecomponentDidUpdate内重复调用setState时,可能会发生这种情况。React 限制嵌套更新的数量以防止无限循环。React 路由使用状态跟踪用户在应用旅程中的位置,因此,React 由于重定向多次尝试更新状态时,会出现前面的错误。使用重定向时,需要确保它不会导致无限的重定向循环。

推道具

<Redirect>组件通过调用history.replace(<path>)将用户重定向到给定路径,即用新路径替换历史堆栈中的当前条目。通过在<Redirect>组件中指定推送道具,调用 history.push 而不是history.replace

<Redirect to="/dashboard" push />

保护路由和授权

使用<Route>组件定义的路由可以通过浏览器的 URL 访问,通过<Link><NavLink>导航到路由,或者通过<Redirect>组件重定向用户。但是,在大多数应用中,某些路由应该仅可供授权或登录的用户访问。例如,假设/user路径显示登录用户的数据;此路径应受到保护,并且仅允许登录用户访问路由。在这些情况下,当您尝试访问路径/user时,<Redirect>组件可以方便地将用户重定向到登录页面(路径/login)。

为了演示这一点,我们创建一个名为UserComponent的组件,当您尝试访问路径/user时,将呈现该组件:

export class UserComponent extends Component {
    render() {
        const { location } = this.props;
        return (
            <div>
                Username: {location && location.state ? location.state.userName 
                 : ''} <br />
                From: {location && location.state ? location.state.from : ''} 
                 <br />
                <button onClick={this.logout}>LOGOUT</button>
            </div>
        )
    }
}

从前面的代码片段中,我们可以看到,UserComponent显示 this.props.location和注销按钮中可用的状态信息。

要检查用户是否已登录,应向服务器发出请求,以检查用户的会话是否存在。然而,在我们的例子中,通过参考浏览器的localStorage中的一个变量来检查用户是否登录:

export class UserComponent extends Component {
    state = {
       isUserLoggedIn: false
    }
    componentWillMount() {
        const isUserLoggedIn = localStorage.getItem('isUserLoggedIn');
        this.setState({isUserLoggedIn});
    }
    render() {
    ...
    }
}

在这里,组件的状态属性isUserLoggedIn将使用存储在同名 localStorage 变量中的值进行更新。

下一步是在UserComponent类的 render 函数中使用此状态信息,并使用<Redirect>组件重定向用户:

export class UserComponent extends Component {
    ...
    render() {
        const { location } = this.props;
        if (!this.state.isUserLoggedIn) {
            return (
                <Redirect to="/login" />
            );
        }
        ...
    }
}

这里,检查状态属性isUserLoggedIn的值,如果其计算结果为 false,或者如果未找到,则将用户重定向到路径为'/login'的路由。

最后一步是实现logout功能,当用户单击注销按钮时调用该功能:

export class UserComponent extends Component {
    logout = (event) => {
        localStorage.removeItem('isUserLoggedIn');
        this.setState({ isUserLoggedIn: false });
    }
    ...
}

注销用户需要删除localStorage变量,并将状态属性isUserLoggedIn更新为'false'

通过这些更改,当状态属性isUserLoggedIn设置为 false 时,UserComponent被重新提交,用户被重定向到路径/login,要求用户提供访问页面的凭据。另外,现在当您尝试通过在浏览器的地址栏中输入路径/user来访问路径<Route>时,<Route>与其路径属性/user将匹配。但是,当呈现UserComponent时,状态属性isUserLoggedIn将计算为 false,从而将用户重定向到/login页面。

使用回调路由重定向

当您尝试访问受保护的<Route>时,您将被重定向到登录页面以提供凭据。提供凭据后,应将您重定向到先前尝试访问的页面。例如,当您尝试在路径/stocks上访问受保护的路由时,您将被重定向到路径/login,然后,在提供正确的凭据后,您应该被重定向到您先前尝试访问的同一路径/stocks。但是,从上一个示例中,您将被重定向到路径/user,并显示用户的配置文件信息。所需的行为将被重定向到受保护的路由/stocks,而不是路径/user

这可以通过在重定向用户时提供状态信息来实现

StocksComponent(由于<Route>匹配/stocks而呈现的组件)中,当您将用户重定向到登录页面时,请在 to prop 中提供状态信息:

export class StocksComponent extends Component {
    ...
    render() {
        const {match } = this.props;
        if (!this.state.isUserLoggedIn) {
            return (
                <Redirect 
                    to={{
                        pathname: "/login",
                        state: { callbackURL: match.url }
                    }}
                />
            )
        }

        return (
            <div>
                In StocksComponent
            </div>
        )
    }
}

在组件的呈现功能中,使用<Redirect>组件将用户重定向到登录页面。这里的<Redirect>组件包括一个 to 道具,指定用户应该重定向到的pathname,它还包括一个提及callbackURL属性的状态对象。callbackURL属性的值为match.url,即当前浏览器 URL 路径/stocks

然后可以在LoginComponent中使用该状态信息将用户重定向到路径/stocks

export class LoginComponent extends Component {
    ...
    render() {
        const { location: { state } } = this.props;
        if (this.state.isUserLoggedIn) {
            return (
                <Redirect 
                    to={{
                        pathname: state && 
                        state.callbackURL || "/user",
                        state: {
                            from: this.props.match.url,
                            userName: this.state.userName
                        }
                    }} 
                />
            )
        }
        ...
    }
}

这里,当用户提供访问受保护路由的凭证时,<Redirect>组件将用户重定向到state.callbackURL中提到的路径。如果callbackURL不可用,用户将被重定向到默认路径,该路径被重定向到路径/user

路由组件道具、match.url和 location.state 的组合可用于将用户重定向到先前请求的受保护路由。

与组件的独占路由

当 URL 呈现给<BrowserRouter>时,它将查找使用<Route>组件创建的路由,并呈现与浏览器 URL 路径匹配的所有路由。例如,考虑以下路由:

<Route
    path="/login"
    component={LoginComponent}
/>
<Route
    path="/:id"
    render={({ match }) => 
        <div> Route with path {match.url}</div>
    }
/>

这里,路径为/login/:id的两条路由都与/loginURL 路径匹配。React Router 呈现与 URL 路径匹配的所有<Route>组件。但是,为了仅呈现第一个匹配路由,库提供了<Switch>组件。<Switch>组件接受<Route>组件列表作为其子组件,并且仅呈现与浏览器 URL 匹配的第一个<Route>

<Switch>
    <Route
        path="/login"
        component={LoginComponent}
    />
    <Route
        path="/:id"
        render={({ match }) =>
            <div> Route with path {match.url}</div>
        }
    />
</Switch>

通过将组件列表包装在组件中,React Router 会依次搜索与浏览器 URL 路径匹配的。一旦找到匹配的,则停止搜索并呈现匹配的。

在前面的示例中,中的第一个仅在浏览器的 URL 路径为/login 且除/login 以外的路径(/123、/products、/stocks 等)将匹配第二个路径并呈现相应的组件时才会呈现

If the order of the previous two components is swapped (that is, the with path /:id is listed above the with path /login), the with path /login would never get rendered because  allows only one and the first matching route to be rendered.

中部件的订购

<Switch><Route>组件的顺序很重要,因为<Switch>组件按顺序查找匹配的<Route>,一旦找到匹配浏览器 URL 的<Route>组件,它就会停止搜索。此行为可能不需要,您可能希望呈现<Switch>中列出的另一条路由。但是,可以通过改变<Switch>中列出<Route>的顺序来纠正:

在以下示例中,提到了在<Switch>中列出组件的一些常见错误:

路径“/”为中的第一个子项

考虑下面的代码片段:

<Switch>
    <Route
        path="/"
        component={LoginComponent}
    />
    <Route
        path="/dashboard"
        component={DashboardComponent}
    />
</Switch>

如果浏览器的 URL 路径为/dashboard,则会将第一个<Route>与路径/匹配,而将永远不会匹配并呈现路径/dashboard<Route>。要解决此问题,请包含确切的道具或列出路径为/<Route>作为<Switch>中的最后一个条目

带路径参数

在下面的代码段中,带有路径参数的<Route>被列为第二个条目:

<Switch>
    <Route
        path="/github"
        component={LoginComponent}
    />
    <Route
        path="/github/:userId"
        component={DashboardComponent}
    />
</Switch>

在前面的示例中,路径为/github<Route>将匹配 URL 路径/github以及路径/github/mjackson;因此,即使具有特定路径的<Route>可用,也呈现第一<Route>。要解决此问题,请提供确切的道具或在路径为/github/:userId<Route>下方列出路径为/github<Route>

从上一段中提到的两种情况来看,将具有特定路径的<Route>组件列在具有通用路径的<Route>组件之上可以避免出现不期望的结果。

添加 404–未找到页面

如前所述,<Switch>组件按顺序查看所有<Route>组件以查找匹配项,并在找到路径与浏览器 URL 匹配的<Route>后停止搜索。这不同于页面中的列表<Route>,其中每个匹配的<Route>都被呈现。因此,<Switch>非常适合呈现Page Not Found页面,也就是说,当提到的<Route>中没有一个与浏览器 URL 匹配时,呈现组件。

让我们将一个没有路径道具的<Route>作为<Switch>中的最后一个条目:

<Switch>
    <Route
        path="/login"
        component={LoginComponent}
    />
    <Route
        path="/user"
        render={({ match }) =>
            <div> Route with path {match.url}</div>
        }
    />
    <Route
        render={({ location }) =>
            <div> 404 - {location.pathname} not 
            found</div>
        }
    />
</Switch>

从前面的代码片段中,我们可以看到,当带有路径属性的<Route>与浏览器的 URL 不匹配时,最后一个没有路径属性的<Route>将匹配并呈现。

重要的是要将Page Not Found <Route>作为最后一个条目,因为<Switch>组件在找到匹配的<Route>后会停止搜索。在前一种情况下,如果没有道具的<Route>包含在其他<Route>之上,则即使列表中存在与浏览器 URL 匹配的<Route>,也会呈现Page Not Found路由。

您也可以指定一个路径属性值为*<Route>,而不是没有路径属性的<Route>,以呈现Page Not Found页面:

<Switch>
    ...
    <Route
        path="*"
        render={({ location }) =>
            <div> 404 - {location.pathname} not 
            found</div>
        }
    />
</Switch>

在这两种情况下,路径都将匹配浏览器的 URL 并呈现Page Not Found页面。

使用中的重定向到未找到的页面

<Switch>组件的子组件也可以包括<Route><Redirect>组件的列表。当<Redirect>组件作为<Switch>中的最后一个条目包含时,如果<Redirect>组件上面提到的<Route>都与浏览器的 URL 不匹配,则将用户重定向到给定路径:

<Switch>
    <Route
        path="/login"
        component={LoginComponent}
    />
    <Route
        path="/user"
        render={({ match }) =>
            <div> Route with path {match.url}</div>
        }
    />
    <Redirect to="/home" />
</Switch>

前面提到的<Redirect>组件将用户重定向到路径为/home<Route>。这类似于显示一个404: Page Not Found page;用户将被重定向到不同的路由,而不是在线显示组件。

例如,如果浏览器的 URL 路径为/dashboard,则前两条路径(路径为/login/user)不匹配,因此使用<Switch>中提到的<Redirect>组件重定向用户。

从旧路径重定向到新路径

<Redirect>组件还可用于将用户从给定路径重定向到新路径。<Redirect>组件接受一个 prop,from,可用于指定与浏览器 URL 匹配的路径,用户应从该路径重定向。此外,应在 to prop 中指定用户应重定向到的路径:

<Switch>
    <Route
        path="/login"
        component={LoginComponent}
    />
    <Route
        path="/user"
        render={({ match }) =>
            <div> Route with path {match.url}</div>
        }
    />
    <Redirect
        from="/home"
        to="/login"
    />
    <Redirect to="/home" />
</Switch>

从前面的示例中,我们可以看到,当浏览器的 URL 路径为/home时,带有 From prop 的<Redirect>组件将匹配给定路径,并使用路径/login将用户重定向到<Route>

当站点上的某些页面被移动到新目录时,<Redirect>组件 from prop 非常有用。例如,如果用户页面已移动到新的目录路径settings/user,则<Redirect from="/user" to="/settings/user" />将用户重定向到新路径。

总结

<Redirect>组件可用于将用户从当前呈现的路由重定向到新路由。组件接受道具:to 和 push。当应用中的组件已移动到其他目录时,或者当用户无权访问页面时,可以使用此重定向。当用户访问受保护的路由且仅允许授权用户查看页面时,<Redirect>组件非常有用。

<Route>列表中只有一个<Route>需要呈现时,使用<Switch>组件。<Switch>组件接受<Route><Redirect>组件列表作为其子组件,并依次搜索匹配的<Route><Redirect>组件。当找到匹配项时,<Switch>渲染组件并停止寻找匹配路径

可以利用<Switch>的这种行为来构建404: Page Not Found,当<Switch>中列出的<Route>组件都不匹配浏览器的 URL 路径时,就会呈现404: Page Not Found。通过将不带任何路径道具的<Route>列为<Switch>中的最后一个条目,如果上面列出的<Route>组件都不匹配浏览器的 URL 路径,则呈现<Route>。或者,当<Switch>中的<Route>组件不匹配时,<Redirect>组件可以作为最后一个条目列出,以将用户重定向到页面。