React Router 提供<Link>
和<NavLink>
组件,允许您导航到应用中定义的不同路由。这些导航组件可以看作是页面上的锚链接,允许您导航到站点中的其他页面。在传统网站中,当您使用锚链接浏览应用时,会导致页面刷新,页面中的所有组件都会重新呈现。使用<Link>
和<NavLink>
创建的导航链接不会导致页面刷新,只有使用<Route>
定义并匹配 URL 路径的页面特定部分才会更新。
与<Route>
组件类似,导航组件<Link>
和<NavLink>
是 React 组件,允许您以声明方式定义导航链接。
在本章中,我们将了解用于导航到应用中定义的路由的各种选项。这包括:
<Link>
组件及其道具<NavLink>
组件及其道具- 使用
match
道具导航到嵌套路由 - 使用
history
以编程方式导航到路由 - 使用高阶组件
withRouter
- 使用
<Prompt>
组件防止路由转换
<Link>
组件用于导航到使用<Route>
组件定义的<indexentry content=" component:about">
现有路由。要导航到路由,请将路由中使用的路径名指定为to
属性的值:
import { Link } from 'react-router-dom';
class App extends Component {
render() {
return (
<div class="container">
<nav>
<Link to="/">Home</Link>
<Link to="/dashboard">Dashboard</Link>
</nav>
<Route
path="/"
component={HomeComponent}
exact
/>
<Route
path="/dashboard"
component={DashboardComponent}
/>
</div>
);
}
}
请注意,to
道具的值与<Route>
中分配给path
道具的值相同。该页面现在呈现两个链接:
当您单击主页时,您将看到主页路由内的文本显示,当您单击仪表板时,您将被导航到路由,其path
道具设置为/dashboard
。
当您使用<Link>
导航到路由时,会调用history.push()
,这会在历史堆栈中添加一个条目。因此,当您单击浏览器的“后退”按钮时,将导航到您正在访问的上一条路由(主路由)。如前一章所述,React Router 使用history
库来维护应用的状态,因为用户在应用旅程中通过各种路由。
<Link>
组件还有另外两个道具——replace
和innerRef
<Link>
中的replace
道具调用history.replace()
,用to
道具中提到的新路径名替换历史堆栈中的当前条目:
<Link to="/dashboard" replace>Dashboard</Link>
例如,如果您在路径/home
处访问页面,访问前面的链接会将历史堆栈中的当前条目替换为/dashboard
,这基本上将条目/home
替换为/dashboard
React 提供ref
以获取对呈现 DOM 元素的引用。然后可以使用该参考(ref
来执行常规流之外的某些操作,例如聚焦于输入元素、媒体播放等。<Link>
是一个复合组件,它在 DOM 上呈现锚元素
前面代码片段中提到的<Link>
组件转换为锚元素,如下所示:
..
<nav>
<a href="/">Home</a>
<a href="/dashboard">Dashboard</a>
</nav>
..
为了获得对该渲染锚元素的引用,将道具innerRef
添加到<Link>
:
<nav>
<Link
to="/"
innerRef={this.refCallback}>
Home
</Link>
<Link
to="/dashboard"
innerRef={this.refCallback}>
Dashboard
</Link>
</nav>
innerRef
prop 接受回调函数作为其值;这里,函数refCallback
被指定为innerRef
属性的值。refCallback
获取对<Link>
组件内部元素的引用:
refCallback(node) {
node.onmouseover = () => {
node.focus();
}
}
当<Link>
组件挂载时,调用回调函数refCallback
。从前面的代码片段中,我们可以看到两个<Link>
组件呈现的锚元素都添加了mouseover
处理程序。当用户将鼠标悬停在链接上时,相应的锚点将获得焦点。
to
道具可以是字符串或对象。对象可以包含以下属性:
pathname
:要导航到的路径search
:路径的查询参数,以字符串值表示hash
:要添加到 URL 的哈希字符串state
:包含呈现组件可以使用的状态信息的对象
使用这些参数,我们添加一个<Link>
组件:
<Link
to={{
pathname: '/user',
search: '?id=1',
hash: '#hash',
state: { isAdmin: true }
}}>
User
</Link>
前面的代码转换为以下内容:
<a href="/user?id=1#hash">User</a>
state
信息不包含在 URL 路径中;但是,它可用于作为<Route>
匹配结果渲染的组件:
<Route
path="/user"
render={({ location }) => {
const { pathname, search, hash, state } = location;
return (
<div>
Inside User route
<h5>Pathname: {pathname}</h5>
<h5>Search: {search}</h5>
<h5>Hash: {hash}</h5>
<h5>State: {'{'}
{Object.keys(state).map((element, index) => {
return (
<span key={index}>
{element}: {state[element].toString()}
</span>
)
})}
{'}'}
</h5>
</div>
);
}}
/>
location
对象包含所有先前定义的参数,包括state
对象。
当用户在应用中导航时,state
对象可用于存储数据,并将该数据提供给<Route>
匹配的下一个渲染组件。
<NavLink>
组件与<Link>
组件类似,只是可以指定几个道具来帮助您有条件地向渲染元素添加样式属性。它接受与<Link>
组件(to
、replace
和innerRef
相同的一组用于导航到路由的道具,并包括用于设置所选路由样式的道具。
让我们来看看这些帮助你风格的组件。
默认情况下,类名active
应用于激活的<NavLink>
组件。例如,当点击<NavLink>
并呈现相应的路由时,所选<NavLink>
的类名设置为active
。要更改此类名,请在<NavLink>
组件上指定activeClassName
属性,并将其值设置为要应用的 CSS 类名:
<nav>
<NavLink to="/">Home</NavLink>
<NavLink
to="/dashboard"
activeClassName="selectedLink">
Dashboard
</NavLink>
</nav>
下一步是在应用的 CSS 文件中指定 CSS 类selectedLink
的样式。注意,第一个<NavLink>
没有指定activeClassName
道具。在这种情况下,当点击<NavLink>
时,添加active
类:
<nav>
<a class="active" aria-current="page" href="/">Home</a>
<a aria-current="page" href="/dashboard">Dashboard</a>
</nav>
但是,当点击第二个<NavLink>
时,应用selectedLink
类:
<nav>
<a aria-current="page" href="/">Home</a>
<a class="selectedLink" aria-current="page" href="/dashboard">Dashboard</a>
</nav>
activeStyle
道具也用于设置所选<NavLink>
的样式。但是,在选择<NavLink>
时,可以内联提供 CSS 样式属性,而不是提供要应用的类:
<NavLink
to="/user"
activeStyle={{
background: 'red',
color: 'white'
}}>
User
</NavLink>
当您使用to
道具/dashboard
点击<NavLink>
时,active
类(或activeStyle
道具中指定的内联样式)将应用于页面中的两个<NavLink>
组件。与<Route>
组件类似,/dashboard
中的/
匹配to
属性中指定的路径,因此活动类应用于<NavLink>
组件。
在这种情况下,只有当路径匹配浏览器的 URL 时,exact
道具才能用于应用active
类或activeStyle
:
<NavLink
to="/"
exact>
Home
</NavLink>
<NavLink
to="/dashboard"
activeClassName="selectedLink">
Dashboard
</NavLink>
<NavLink>
组件还支持strict
道具,可用于匹配to
道具中指定的尾部斜线:
<NavLink
to="/dashboard/"
activeClassName="selectedLink"
strict>
Dashboard
</NavLink>
这里,仅当浏览器的 URL 路径与路径/dashboard/
匹配时,类selectedLink
才应用于<NavLink>
组件—例如,当 URL 中存在尾随斜杠时。
isActive
道具用于确定<NavLink>
组件是否应应用active
类(或activeStyle
道具中指定的内联样式)。指定为isActive
属性值的函数应返回布尔值:
<NavLink
to={{
pathname: '/user',
search: '?id=1',
hash: '#hash',
state: { isAdmin: true }
}}
activeStyle={{
background: 'red',
color: 'white'
}}
isActive={(match, location) => {
if (!match) {
return false;
}
const searchParams = new URLSearchParams(location.search);
return match.isExact && searchParams.has('id');
}}>
User
</NavLink>
从前面的示例中,函数接受两个参数-match
和location
。activeStyle
道具中定义的样式仅在match.isExact && searchParams.has('id')
条件为 true 时应用,因此,仅当match
为exact
且 URL 有查询参数id
时应用。
当浏览器的 URL 为/user
时,显示用<Route>
定义的对应路由。但是,<NavLink>
组件将具有默认样式,而不是activeStyle
属性中提到的样式,因为缺少查询参数id
。
<NavLink>
中的isActive
函数接收浏览器的历史记录location
并确定浏览器的location.pathname
是否与给定条件匹配。要提供不同的位置,请包括location
道具:
<NavLink
to="/user"
activeStyle={{
background: 'red',
color: 'white'
}}
location={{
search: '?id=2',
}}
isActive={(match, location) => {
if (!match) {
return false;
}
const searchParams = new URLSearchParams(location.search);
return match.isExact && searchParams.has('id');
}}>
User
</NavLink>
注意,to
道具没有指定search
参数;但是,location
属性包含它,因此,当浏览器的位置为/user
时,isActive
函数返回 true,因为搜索参数包含id
属性。
在上一章中,我们看到了如何使用渲染组件接收的match
属性创建嵌套路由。match.url
属性包含与<Route>
组件路径匹配的浏览器 URL 路径。类似地,<Link>
和<NavLink>
组件可用于创建导航链接以访问这些嵌套路由:
<nav>
<Link
to={`${match.url}/pictures`}>
Pictures
</Link>
<NavLink
to={`${match.url}/books`}
activeStyle={{
background: 'orange'
}}>
Books
</NavLink>
</nav>
在前面的代码片段中,<Link>
和<NavLink>
组件利用match.url
获取对当前渲染路由的引用,并添加导航到嵌套路由所需的其他路径值
<Link>
和<NavLink>
组件在页面上呈现锚定链接,允许您从当前路由导航到新路由。但是,在许多情况下,当事件发生时,应该通过编程方式将用户导航到新路由。例如,在登录表单中单击 Submit 按钮时,应将用户导航到新路由。在以下情况下,可以使用渲染组件可用的history
对象:
export const DashboardComponent = (props) => (
<div className="dashboard">
Inside Dashboard route
<button onClick={() => props.history.push('/user')}>
User
</button>
</div>
);
这里,DashboardComponent
接收props
作为其参数,其中包含history
对象。onClick
处理程序使用路径名/user
调用props.history.push
。此调用向历史堆栈添加一个条目,并使用路径/user
将用户导航到<Route>
。history
对象也可以用来替换历史堆栈中的当前条目,方法是使用history.replace
代替history.push
。
使用<Route>
匹配渲染的组件可以使用history
对象。在前面的示例中,DashboardComponent
是导航到路径/dashboard
的结果。渲染组件接收到包含history
对象的props
(以及match
、location
和staticContext
)。如果页面上呈现的组件不是路由导航的结果,history
对象对该组件不可用。
考虑一个包含在 To1 T1:
class FooterComponent extends Component {
render() {
return (
<footer>
In Footer
<div>
<button
onClick={() =>
this.props.history.push('/user')}>
User
</button>
<button
onClick={() =>
this.props.history.push('/stocks')}>
Stocks
</button>
</div>
</footer>
)
}
}
FooterComponent
有两个按钮,它们调用history.push
来导航到应用中的一个页面。点击按钮,抛出错误TypeError: Cannot read property 'push' of undefined
。抛出此错误是因为props
属性中的history
对象不可用,因为导航不会呈现组件。要避免这种情况,请使用高阶组件withRouter
:
export const Footer = withRouter(FooterComponent);
在这里,react-router
包中定义的withRouter
函数接受 React 组件作为其参数,并对其进行扩充,以提供props
属性上的必要对象—history
、match
、location
和staticContext
。
关于 HOC 的 React 文档:高阶组件是接受组件并返回新组件的函数**。**虽然组件将道具转换为 UI,但高阶组件将一个组件转换为另一个组件。
withRouter
HOC 中封装的组件可以使用<Route>
、<Link>
和<NavLink>
定义路由和导航链接:
import { withRouter } from 'react-router';
class FooterComponent extends Component {
render() {
return (
<footer>
In Footer
<div>
<button onClick={() =>
this.props.history.push('/user')}>User</button>
<button onClick={() =>
this.props.history.push('/stocks')}>Stocks</button>
<Link to='subroute'>User</Link>
<Route
path='/subroute'
render={() => {
return <span>Inside Footer Subroute</span>
}} />
</div>
</footer >
)
}
}
export const Footer = withRouter(FooterComponent);
在前面的代码片段中,withRouter
HOC 使组件能够获取路由的上下文,从而使Link
、NavLink
和Route
等组件可用
当您在应用中的页面之间导航时,会立即转换到新路由。但是,在某些情况下,您希望根据应用的状态阻止此转换。一个常见的例子是,用户在表单字段中输入数据,并花费数分钟(或数小时)填充表单数据。如果用户意外单击导航链接,则表单中输入的所有数据都将丢失。应将此路由导航通知用户,以便用户有机会保存输入表单的数据。
传统网站跟踪表单的状态,并在用户试图从包含尚未提交到服务器的表单的页面导航时显示确认消息。在这些场景中,将显示一个确认对话框,其中包含两个选项,确定和取消;前一个选项允许用户过渡到下一步,后一个选项取消过渡:
React Router 提供<Prompt>
组件,可用于显示确认对话框,防止用户意外离开当前<Route>
:
import { Prompt } from 'react-router-dom'
<Prompt
when={this.state.isFormSubmitted}
message='Are you sure?'
/>
这里的<Prompt>
组件接受两个道具—when
和message
。从前面的代码片段可以看出,如果state
属性isFormSubmitted
的值为true
,并且当用户尝试导航离开当前路由时,将向用户显示一个带有消息“您确定吗?”的确认对话框
Please note, the <Prompt>
message is shown only when the user tries to navigate away from the current route. No message is shown when the state
property is set to true
.
分配给when
道具的值可以是任何布尔变量或布尔值。在 React 中,组件的state
用作视图模型,以维护渲染组件的状态。在这样的情况下,state
属性非常理想,可以确定当用户试图离开当前路由时是否应显示<Prompt>
。
message
属性的值可以是字符串或函数:
<Prompt
when={this.state.isFormSubmitted}
message={(location) => 'Are you sure you want to navigate to ${location.pathname}?'} />
该函数接收location
参数,该参数包括用户试图导航到的路由的位置信息
Similar to other components in the 'react-router-dom'
package, the <Prompt>
component should be used inside a rendered <Route>
. When you try to use a <Prompt>
without it having the context of the current route, the message. You should not use <Prompt>
outside a <Router>
is shown.
当用户试图离开当前路由时(无论应用的state
如何),也可以通过不包括when
道具来显示消息:
<Prompt message='Are you sure?' />
通常情况下,when
道具包含在<Prompt>
中,分配给when
道具的值用于确定是否应显示确认对话框。
When you're trying these examples, ensure that you have only one <Prompt>
for the given <Route>
, else the library will report the warning A history supports only one prompt at a time
.
在本章中,我们了解了如何使用<Link>
和<NavLink>
导航组件导航到应用中定义的各种路由。这些组件在页面中呈现anchor
链接,当用户点击这些链接时,页面的各个部分都会更新,而不是完全重新加载页面,从而提供清晰的用户体验。<Link>
组件接受道具to
、replace
和innerRef
<NavLink>
组件与<Link>
组件类似,它接受<Link>
组件使用的所有道具。除了添加页面链接外,<NavLink>
组件还接受多个道具—activeClassName
、activeStyle
、exact
、strict
和isActive
。
为了创建到嵌套路由的链接,<Link>
和<NavLink>
组件可以使用to
属性中的前缀match.url
。此外,您还可以在事件处理程序函数中使用history.push
或history.replace
以编程方式导航。道具-history
、match
、location
和staticContext
-可通过withRouter
高阶组件提供给路由上下文之外呈现的组件。'react-router-dom'
软件包包括一个<Prompt>
组件,当用户试图通过意外单击导航链接导航到路由时,该组件可用于显示确认对话框。<Prompt>
组件接受when
和message
属性,并且基于分配给when
属性的布尔值,message
属性中指定的消息将显示给用户
在第 4 章中,我们将使用重定向和切换组件来了解<Redirect>
和<Switch>
组件。此外,我们还将了解如何使用这些组件来保护路由,并在页面中没有任何路由与请求的 URL 匹配时显示未找到的页面。