九、使用路由处理导航

几乎每个 web 应用都需要路由。它是基于一组路由配置响应给定 URL 的过程。换句话说,从 URL 到呈现内容的简单映射。然而,这个简单的任务比最初看起来要复杂得多。这就是为什么我们要利用本章中的react-router包,即 React 的事实上的路由工具。

首先,您将学习使用 JSX 语法声明路由的基础知识。然后,我们将深入研究路由的动态方面,例如动态路径段和查询参数。接下来,您将使用react-router中的组件实现链接。我们将以一个示例结束本章,该示例演示如何在 URL 更改时延迟加载组件。

申报路由

如果您曾经在 React 之外处理过路由,您可能已经知道它可能很快就会变得混乱。有了react-router,将路由与它们呈现的内容搭配起来就容易多了。在本节中,您将看到这在很大程度上归因于用于定义路由的声明性 JSX 语法。

我们将创建一个基本的 hello world 示例路由,这样您就可以了解 React 应用中路由的基本情况。然后,我们将研究如何通过特性而不是在单片模块中组织路由声明。最后,您将实现一个通用的父子路由模式。

你好

让我们创建一个呈现简单组件的简单路由。首先,我们有一个最简单的 React 组件,我们希望在激活路由时渲染该组件:

import React from 'react'; 

export default () => ( 
  <p>Hello Route!</p> 
); 

接下来,让我们看看路由定义本身:

import React from 'react'; 
import { 
  Router, 
  Route, 
  browserHistory, 
} from 'react-router'; 

import MyComponent from './MyComponent'; 

// Exports a "<Router>" component to be rendered. 
export default ( 
  <Router history={browserHistory}> 
    <Route path="/" component={MyComponent} /> 
  </Router> 
); 

如您所见,此模块导出一个 JSX<Router>元素。这实际上是应用的顶级组件,稍后您将看到。但首先,让我们分析一下这里发生的事情。browserHistory对象用于告诉路由它应该使用本机浏览器历史 API 来跟踪导航。不要太担心这个财产;大多数情况下,您将使用此选项。

在路由中,我们将实际路由声明为<Route>元素。任何路由都有两个关键属性:pathcomponent。这些都是不言自明的,当path与活动 URL 匹配时,component被呈现。但它到底是在哪里渲染的呢?

为了帮助回答这个问题,让我们来看看应用的主要模块:

import React from 'react'; 
import { render } from 'react-dom'; 

// Imports the "<Router>", and all the "<Route>" 
// elements within. 
import routes from './routes'; 

// The "<Router>" is the root element of the app. 
render( 
  routes, 
  document.getElementById('app') 
); 

正如我前面提到的,路由是顶级组件。路由本身实际上并不呈现任何内容,它只负责管理基于当前 URL 呈现其他组件的方式。果然,当您在浏览器中查看此示例时,<MyComponent>按预期呈现:

Hello route

这就是 React 中路由工作原理的要点。现在,让我们从功能的角度来看声明路由。

解耦路由声明

如前一节所示,使用react-router声明路由非常简单。当您的应用增长并且几十条路由都在一个模块中声明时,困难就来了,因为在心里将一条路由映射到一个功能更加困难。

为了帮助实现这一点,应用的每个顶级功能都可以定义自己的routes模块。这样,就可以清楚地知道哪些路线属于哪个功能。那么,让我们从根路由模块开始,看看它在引入特定于功能的路由时是什么样子的:

import React from 'react'; 
import { 
  Router, 
  browserHistory, 
} from 'react-router'; 

// Import the routes from our features. 
import oneRoutes from './one/routes'; 
import twoRoutes from './two/routes'; 

// The feature routes are rendered as children of 
// the main router. 
export default ( 
  <Router history={browserHistory}> 
    {oneRoutes} 
    {twoRoutes} 
  </Router> 
); 

在本例中,应用有两个显著命名的特性:一个和两个。oneRoutestwoRoutes值从各自的功能目录中拉入,并作为子元素呈现在<Router>元素中。现在,这个模块只会增加应用功能的数量,而不是路由的数量,路由的数量可能会大得多。现在让我们来看一个特性路由:

import React from 'react'; 
import { Route, IndexRedirect } from 'react-router'; 

// The pages that make up feature "one". 
import First from './First'; 
import Second from './Second'; 

// The routes of our feature. The "<IndexRedirect>" 
// handles "/one" requests by redirecting to "/one/1". 
export default ( 
  <Route path="/one"> 
    <IndexRedirect to="1" /> 
    <Route path="1" component={First} /> 
    <Route path="2" component={Second} /> 
  </Route> 
); 

此模块导出单个<Route>元素。此功能使用的每条路线都是一个子<Route>。例如,如果运行此示例并将浏览器指向/one/1,您将看到First的渲染内容。

Decoupling route declarations

<IndexRedirect>元素需要重定向/one/one/1

在本章末尾,我们将详细介绍如何构造路由和应用功能。但首先,我们需要更深入地思考父路径和子路径。

父和子路由

上例中的App组件是应用的主要组件。这是因为它定义了根 URL:/。然而,一旦用户导航到特定的功能 URL,App组件就不再相关。

路由的挑战在于,有时您需要一个父组件,例如App,这样您就不必继续重新呈现页面骨架。所谓骨架,我指的是应用的每个页面都通用的内容。在这个场景中,我们总是希望App进行渲染。当 URL 更改时,只有App组件的某些部分呈现新内容。下面是一张图表,说明了这个想法:

Parent and child routes

随着 URL 的更改,父组件将填充相应的子组件。从App组件开始,让我们了解如何进行此设置:

import React, { PropTypes } from 'react'; 

// The "header" and "main" properties are the rendered 
// components specified in the route. They're placed 
// in the JSX of this component - "App". 
const App = ({ header, main }) => ( 
  <section> 
    <header> 
      {header} 
    </header> 
    <main> 
      {main} 
    </main> 
  </section> 
); 

// The "header" and "main" properties should be 
// a React element. 
App.propTypes = { 
  header: PropTypes.element, 
  main: PropTypes.element, 
}; 

现在我们已经定义了页面框架。现在由路由配置来正确呈现App。现在让我们看看这个配置:

import React from 'react'; 
import { 
  Router, 
  Route, 
  browserHistory, 
} from 'react-router'; 

// The "App" component is rendered with every route. 
import App from './App'; 

// The "User" components rendered with the "/users" 
// route. 
import UsersHeader from './users/UsersHeader'; 
import UsersMain from './users/UsersMain'; 

// The "Groups" components rendered with the "/groups" 
// route. 
import GroupsHeader from './groups/GroupsHeader'; 
import GroupsMain from './groups/GroupsMain'; 

// Configures the "/users" route. It has a path, 
// and named components that are placed in "App". 
const users = { 
  path: 'users', 
  components: { 
    header: UsersHeader, 
    main: UsersMain, 
  }, 
}; 

// Configures the "/groups" route. It has a path, 
// and named components that are placed in "App". 
const groups = { 
  path: 'groups', 
  components: { 
    header: GroupsHeader, 
    main: GroupsMain, 
  }, 
}; 

// Setup the router, using the "users" and "groups" 
// route configurations. 
export default ( 
  <Router history={browserHistory}> 
    <Route path="/" component={App}> 
      <Route {...users} /> 
      <Route {...groups} /> 
    </Route> 
  </Router> 
); 

此应用有两个页面/功能-usersgroups。它们中的每一个都定义了自己的App组件。例如,UsersHeader填入header值,UsersMain填入主值。还要注意,每个路由都有一个components属性,您可以在其中传递headermain值。这就是App如何根据当前路线找到它需要的内容。如果您运行此示例并导航到/users,您将看到以下内容:

Parent and child routes

对于react-router的新手来说,一个常见的错误是认为组件本身被传递给了父组件。实际上,传递给父级的是呈现的 JSX 元素。路由实际呈现内容,然后将内容传递给父级。

办理进路参数

到目前为止,我们在本章中看到的 URL 都是静态的。大多数应用将同时使用静态和动态路由。在本节中,您将学习如何将动态 URL 段传递到组件中,如何使这些段成为可选段,以及如何获取查询字符串参数。

路由中的资源 ID

一个常见的用例是将后端资源的 ID 作为 URL 的一部分。这使得我们的代码很容易获取 ID,然后进行 API 调用以获取相关的资源数据。让我们实现一个呈现用户详细信息页面的路由。这将需要一个包含用户 ID 的路由,然后需要以某种方式将用户 ID 传递给组件,以便组件能够获取用户。

我们将从路线开始:

import React from 'react'; 
import { 
  Router, 
  Route, 
  browserHistory, 
} from 'react-router'; 

import UserContainer from './UserContainer'; 

export default ( 
  <Router history={browserHistory}> 
    { /* Note the ":" before "id". This denotes 
         a dynamic URL segment and the value will 
         be available in the "params" property of 
         the rendered component. */ } 
    <Route path="/users/:id" component={UserContainer} /> 
  </Router> 
); 

:语法标记 URL 变量的开始。因此在本例中,id变量将被传递到UserContainer组件,我们现在将实现该组件:

import React, { Component, PropTypes } from 'react'; 
import { fromJS } from 'immutable'; 

import User from './User'; 
import { fetchUser } from './api'; 

export default class UserContainer extends Component { 
  state = { 
    data: fromJS({ 
      error: null, 
      first: null, 
      last: null, 
      age: null, 
    }), 
  } 

  // Getter for "Immutable.js" state data... 
  get data() { 
    return this.state.data; 
  } 

  // Setter for "Immutable.js" state data... 
  set data(data) { 
    this.setState({ data }); 
  } 

  componentWillMount() { 
    // The dynamic URL segment we're interested in, "id", 
    // is stored in the "params" property. 
    const { params: { id } } = this.props; 

    // Fetches a user based on the "id". Note that it's 
    // converted to a number first. 
    fetchUser(+id).then( 
      // If the user was successfully fetched, then 
      // merge the user properties into the state. Also, 
      // make sure that "error" is cleared. 
      (user) => { 
        this.data = this.data 
          .merge(user, { error: null }); 
      }, 

      // If the user fetch failed, set the "error" state 
      // to the resolved error value. Also, make sure the 
      // other user properties are restored to their defaults 
      // since the component is now in an error state. 
      (error) => { 
        this.data = this.data 
          .merge({ 
            error, 
            first: null, 
            last: null, 
            age: null, 
          }); 
      } 
    ); 
  } 

  render() { 
    return ( 
      <User {...this.data.toJS()} /> 
    ); 
  } 
} 

// Params should always be there... 
UserContainer.propTypes = { 
  params: PropTypes.object.isRequired, 
}; 

params属性包含 URL 的任何动态部分。在本例中,我们对id参数感兴趣。然后,我们将该值的整数版本传递给fetchUser()API 调用。如果 URL 完全缺少该段,那么该代码将根本无法运行;路由只会抱怨找不到匹配的路由。但是,没有在路由级别进行类型检查,这意味着由您来设计故障模式。

在本例中,如果用户传递字符串“one”,则类型转换将失败,并将发生 500 错误。您可以编写一个类型检查参数的函数,该函数不会因异常而失败,而是以 404 错误响应。无论如何,提供有意义的故障模式取决于应用,而不是react-route库。

现在让我们来看一下 API 函数,我们用它来响应用户数据:

// Mock data... 
const users = [ 
  { first: 'First 1', last: 'Last 1', age: 1 }, 
  { first: 'First 2', last: 'Last 2', age: 2 }, 
]; 

// Returns a promise that resolves to a 
// user from the "users" array, using the 
// given "id" index. If nothing is found, 
// the promise is rejected. 
export function fetchUser(id) { 
  return new Promise((resolve, reject) => { 
    const user = users[id]; 

    if (user === undefined) { 
      reject(`User ${id} not found`); 
    } else { 
      resolve(user); 
    } 
  }); 
} 

要么在模拟数据的users数组中找到用户,要么拒绝承诺。如果被拒绝,则调用UsersContainer组件的错误处理行为。最后但并非最不重要的一点是,我们有负责实际呈现用户详细信息的组件:

import React, { PropTypes } from 'react'; 
import { Map as ImmutableMap } from 'immutable'; 

// Renders "error" text, unless "error" is 
// null - then nothing is rendered. 
const Error = ({ error }) => 
  ImmutableMap() 
    .set(null, null) 
    .get(error, (<p><strong>{error}</strong></p>)); 

// Renders "children" text, unless "children" 
// is null - then nothing is rendered. 
const Text = ({ children }) => 
  ImmutableMap() 
    .set(null, null) 
    .get(children, (<p>{children}</p>)); 

const User = ({ 
  error, 
  first, 
  last, 
  age, 
}) => ( 
  <section> 
    { /* If there's an API error, display it. */ } 
    <Error error={error} /> 

    { /* If there's a first, last, or age value, 
         display it. */ } 
    <Text>{first}</Text> 
    <Text>{last}</Text> 
    <Text>{age}</Text> 
  </section> 
); 

// Every property is optional, since we might 
// have have to render them. 
User.propTypes = { 
  error: PropTypes.string, 
  first: PropTypes.string, 
  last: PropTypes.string, 
  age: PropTypes.number, 
}; 

export default User; 

现在,如果运行该示例并导航到/users/0,则呈现的页面如下所示:

Resource IDs in routes

如果您导航到一个不存在的用户,/users/2,您将看到以下内容:

Resource IDs in routes

可选参数

有时,我们需要指定启用某个特性的某些方面。在许多情况下,指定此选项的最佳方法是将该选项编码为 URL 的一部分,或将其作为查询字符串提供。URL 最适用于简单选项,如果组件可以使用许多可选值,则查询字符串最适用。

让我们实现一个呈现用户列表的用户列表组件。或者,我们希望能够按降序对列表进行排序。让我们在此页面的路由定义中将其作为可选路径段:

import React from 'react'; 
import { 
  Router, 
  Route, 
  browserHistory, 
} from 'react-router'; 

import UsersContainer from './UsersContainer'; 

export default ( 
  <Router history={browserHistory}> 
    { /* The ":desc" parameter is optional, and so 
         is the "/" that precedes it. The "()" syntax 
         marks anything that's optional. */ } 
    <Route 
      path="/users(/:desc)" 
      component={UsersContainer} 
    /> 
  </Router> 
); 

正是()语法将两者之间的任何内容标记为可选。在本例中,它是/和-desc参数。这意味着用户可以在/users/之后提供他们想要的任何东西。这也意味着组件需要确保提供了字符串desc,并且忽略了所有其他内容。

组件还可以处理提供给它的任何查询字符串。因此,虽然路由声明没有提供任何机制来定义接受的查询字符串,但路由仍然会处理它们,并以可预测的方式将它们传递给组件。现在让我们来看一看用户列表容器组件:

import React, { Component, PropTypes } from 'react'; 
import { fromJS } from 'immutable'; 

import Users from './Users'; 
import { fetchUsers } from './api'; 

export default class UsersContainer extends Component { 
  // The "users" state is an empty immutable list 
  // by default. 
  state = { 
    data: fromJS({ 
      users: [], 
    }), 
  } 

  componentWillMount() { 
    // The URL and query string data we need... 
    const { 
      props: { 
        params, 
        location: { 
          query, 
        }, 
      }, 
    } = this; 

    // If the "params.desc" value is "desc", it means that 
    // "desc" is a URL segment. If "query.desc" is true, it 
    // means "desc" was provided as a query parameter. 
    const desc = params.desc === 'desc' || !!(query.desc); 

    // Tell the "fetchUsers()" API to sort in descending 
    // order if the "desc" value is true. 
    fetchUsers(desc).then((users) => { 
      this.data = this.data 
        .set('users', users); 
    }); 
  } 

  render() { 
    return ( 
      <Users {...this.data.toJS()} /> 
    ); 
  } 
} 

UsersContainer.propTypes = { 
  params: PropTypes.object.isRequired, 
  location: PropTypes.object.isRequired, 
}; 

这个容器的关键方面是它寻找params.descquery.desc。它将此用作fetchUsers()API 的参数,以确定排序顺序。下面是导航到/users时呈现的内容:

Optional parameters

如果我们通过导航到/users/desc来包含降序参数,我们会得到以下结果:

Optional parameters

使用连杆组件

在本节中,我们将介绍如何创建链接。您可能会尝试使用标准的<a>元素链接到react-router控制的页面。这种方法的问题是,这些链接将试图通过发送 GET 请求在后端定位页面。这不是我们想要的,因为路由配置已经在浏览器中。

我们将从一个非常简单的示例开始,该示例演示了<Link>元素在大多数方面如何与<a>元素相似。然后,您将看到如何构建使用 URL 参数和查询参数的链接。

基本链接

链接的想法很简单。我们使用它来指向路由,这些路由随后呈现新内容。然而,Link组件还负责浏览器历史记录和查找路由。下面是一个呈现两个链接的应用组件:

import React, { PropTypes } from 'react'; 
import { Link } from 'react-router'; 

const App = ({ content }) => ( 
  <section> 
    {content} 
  </section> 
); 

App.propTypes = { 
  content: PropTypes.node.isRequired, 
}; 

// The "content" property has the default content 
// for the "App" component. The "<Link>" elements 
// handle dealing with the history API. Regular 
// "<a>" links result in requests being sent to 
// the server. 
App.defaultProps = { 
  content: ( 
    <section> 
      <p><Link to="first">First</Link></p> 
      <p><Link to="second">Second</Link></p> 
    </section> 
  ), 
}; 

export default App; 

to属性指定单击时要激活的路由。在这种情况下,应用有两条路由-/first/second。下面是渲染链接的外观:

Basic linking

如果单击第一个链接,页面内容将更改为如下所示:

Basic linking

URL 及查询参数

构造传递给<Link>的路径的动态段是简单的字符串操作。路径中的所有内容都在to属性中。这意味着我们必须自己编写更多的代码来构造字符串,但也意味着代表路由的幕后魔法更少。

另一方面,<Link>query属性接受查询参数的映射,并将为您构造查询字符串。让我们创建一个简单的组件,它将回显传递给 echo URL 段或echo查询参数的任何内容:

import React from 'react'; 

// Simple component that expects either an "echo" 
// URL segment parameter, or an "echo" query parameter. 
export default ({ 
  params, 
  location: { 
    query, 
  }, 
}) => ( 
  <h1>{params.echo || query.echo}</h1> 
); 

现在让我们看一下呈现两个链接的 AutoT0.组件。第一个将构建一个使用动态值作为 URL 参数的字符串。第二个将向query属性传递一个对象以构建查询字符串:

import React, { PropTypes } from 'react'; 
import { Link } from 'react-router'; 

const App = ({ content }) => ( 
  <section> 
    {content} 
  </section> 
); 

App.propTypes = { 
  content: PropTypes.node.isRequired, 
}; 

// Link parameter and query data... 
const param = 'From Param'; 
const query = { echo: 'From Query' }; 

App.defaultProps = { 
  content: ( 
    <section> 
      { /* This "<Link>" uses a parameter as part of 
           the "to" property. */} 
      <p><Link to={`echo/${param}`}>Echo param</Link></p> 

      { /* This "<Link>" uses the "query" property 
           to add query parameters to the link URL. */ } 
      <p><Link to="echo" query={query}>Echo query</Link></p> 
    </section> 
  ), 
}; 

export default App; 

下面是两个链接在渲染时的外观:

URL and query parameters

param 链接将您带到/echo/From Param,如下所示:

URL and query parameters

查询链接带您到/echo?echo=From+Query,如下图:

URL and query parameters

延迟路由

在本章前面,您学习了如何按功能拆分路由声明。这样做是为了避免使用单片路由模块,因为单片路由模块不仅难以破译,还可能导致性能问题。考虑一个具有数十个特性和数百条路由的大型应用。必须预先加载所有这些路由和组件将导致糟糕的用户体验。

在本章的最后一节中,我们将详细介绍装载路线。其中一部分涉及利用加载程序,如本例中使用的 Webpack 加载程序。首先,让我们来看看主要的组成部分:

import React, { PropTypes } from 'react'; 
import { Link } from 'react-router'; 

// The "App" component is divided into 
// "header" and "content" sections, and will 
// simply render these properties. 
const App = ({ header, content }) => ( 
  <section> 
    <header> 
      {header} 
    </header> 
    <main> 
      {content} 
    </main> 
  </section> 
); 

// The "header" and "content" properties need to be 
// renderable values. 
App.propTypes = { 
  header: PropTypes.node.isRequired, 
  content: PropTypes.node.isRequired, 
}; 

// The default content for our "App" component. 
App.defaultProps = { 
  header: (<h1>App</h1>), 
  content: ( 
    <ul> 
      <li><Link to="users">Users</Link></li> 
      <li><Link to="groups">Groups</Link></li> 
    </ul> 
  ), 
}; 

export default App; 

这个示例应用有两个特性:出色地命名为usersgroups。在主页上,每个功能都有一个链接。我们知道,这个应用总有一天会成功,并将变得巨大。因此,我们不希望只在用户单击其中一个链接时,预先加载所有路由和组件。

现在让我们来关注主路线模块:

import React from 'react'; 
import { 
  Router, 
  browserHistory, 
} from 'react-router'; 

import App from './App'; 

// Defines the main application route as 
// a plain object, instead of a "<Route>" 
// component. 
const routes = { 
  // The "path" and "component" are specified, 
  // just like any other route. 
  path: '/', 
  component: App, 

  // This allows for lazy route configuration loading. 
  // The "require.ensure()" call allows for tools like 
  // Webpack to split the code bundles into separate 
  // modules that are loaded on demand. The actual 
  // "require()" calls get the route configurations. 
  // In this case, it's routes for the "users" feature 
  // and the "groups" feature. 
  getChildRoutes(partialNextState, callback) { 
    require.ensure([], (require) => { 
      const { users } = require('./users/routes'); 
      const { groups } = require('./groups/routes'); 

      callback(null, [ 
        users, 
        groups, 
      ]); 
    }); 
  }, 
}; 

// The "routes" object is passed to the "routes" 
// property. There are no nested "<Route>" elements 
// needed for this router. 
export default ( 
  <Router history={browserHistory} routes={routes} /> 
); 

App路线的关键方面是getChildRoutes()方法。当需要进行路由查找时,将调用此函数。这是完美的,因为这允许用户与应用交互,而无需加载这些路由,直到他们单击其中一个链接。这被称为延迟加载,因为此代码加载另外两个路由模块。

require.ensure()功能是在 Webpack 等代码绑定器中启用代码拆分的功能。require.ensure()函数告诉绑定器,此回调中的require()调用可以进入一个单独的绑定中,并根据需要加载,而不是创建一个大的绑定。我建议您在为本书下载的代码中运行此示例,并在浏览应用时查看 NetworkDeveloper 工具。

以下是主页呈现时的外观:

Lazy routing

现在,让我们来看看其中的一个特色路由模块:

import React from 'react'; 

import UsersHeader from './UsersHeader'; 
import UsersContent from './UsersContent'; 

// The route configuration for our "users" feature. 
export const users = { 

  // The components rendered by "App" are specified 
  // here as "UsersHeader" and "UsersContent". 
  path: 'users', 
  components: { 
    header: UsersHeader, 
    content: UsersContent, 
  }, 
  childRoutes: [ 

    // The "users/active" route lazily-loads the 
    // "UsersActiveHeader" and "UsersActiveContent" 
    // components when the route is activated. 
    { 
      path: 'active', 
      getComponents(next, cb) { 
        require.ensure([], (require) => { 
          const { 
            UsersActiveHeader, 
          } = require('./UsersActiveHeader'); 

          const { 
            UsersActiveContent, 
          } = require('./UsersActiveContent'); 

          cb(null, { 
            header: UsersActiveHeader, 
            content: UsersActiveContent, 
          }); 
        }); 
      }, 
    }, 

    // The "users/inactive" route lazily-loads the 
    // "UsersInactiveHeader" and "UsersInactiveContent" 
    // components when the route is activated. 
    { 
      path: 'inactive', 
      getComponents(next, cb) { 
        require.ensure([], (require) => { 
          const { 
            UsersInactiveHeader, 
          } = require('./UsersInactiveHeader'); 

          const { 
            UsersInactiveContent, 
          } = require('./UsersInactiveContent'); 

          cb(null, { 
            header: UsersInactiveHeader, 
            content: UsersInactiveContent, 
          }); 
        }); 
      }, 
    }, 
  ], 
}; 

export default users; 

它遵循与主routes模块相同的延迟加载原则。这一次,当找到匹配的路由时,调用的是getComponents()方法。然后加载组件代码包。

以下是Users页面的外观:

Lazy routing

以下是Active Users页面的外观:

Lazy routing

这种技术对于大型应用是绝对必要的。然而,重构现有特性以使用这种延迟加载方法进行路由并不太困难。因此,在应用的早期,不要太担心延迟加载。

总结

在本章中,您学习了 React 应用中的路由。路由的任务是呈现与 URL 对应的内容。react-router包是作业的标准工具。

您了解了路由如何是 JSX 元素,就像它们呈现的组件一样。有时您需要将路由拆分为基于功能的routes模块。构建页面内容的一种常见模式是,有一个父组件在 URL 更改时呈现动态部分。

您学习了如何处理 URL 段和查询字符串的动态部分。您还学习了如何使用<Link>元素在整个应用中构建链接。最后,您看到了大型应用能够通过延迟加载其路由配置和组件来扩展。

在下一章中,您将学习如何在 Node.js 中呈现 React 组件。