React Router 库提供了几个组件来处理各种用例,例如添加带有<Link>
和<NavLink>
的导航链接,使用<Redirect>
组件重定向用户,等等。<BrowserRouter>
组件包装应用的根组件(<App />
,并使这些组件能够与history
对象交互。当应用初始化时,<BrowserRouter>
组件初始化history
对象,并使用 React 的context
将其提供给所有子组件。
单页应用中的路由不是真正的路由;相反,它是组件的条件呈现。<BrowserRouter>
组件创建history
对象,history
对象有push
、replace
、pop
等方法,在导航时使用。当用户在页面之间导航时,history
对象使应用能够维护历史记录。除了<BrowserRouter>
之外,React Router 还提供各种路由实现方式—<HashRouter>
、<StaticRouter>
、<MemoryRouter>
和<NativeRouter>
。这些路由使用react-router
核心包中包含的低级Router
接口
在本章中,我们将了解低级<Router>
组件和各种路由实现:
<Router>
和react-router
包<BrowserRouter>
道具HashRouter
-在传统浏览器中使用的路由实现
其他<Router>
实现,如<StaticRouter>
、<MemoryRouter>
和<NativeRouter>
将在下一章中讨论
如前所述,React Router 提供各种路由实现:
<BrowserRouter>
<HashRouter>
<MemoryRouter>
<StaticRouter>
<NativeRouter>
这些路由使用一个低级接口--<Router>
。<Router>
组件是核心react-router
包的一部分,<Router>
接口提供的功能通过这些路由实现进行扩展
<Router>
组件接受两个道具—history
和children
。history
对象可以是浏览器历史记录的引用,也可以是内存中维护的应用历史记录(这在浏览器历史记录实例不可用的本机应用中很有用)。<Router>
组件接受一个子组件,它通常是应用的根组件。此外,它还创建了一个context
对象context.router
,通过该对象,其所有子组件(如<Route>
、<Link>
、<Switch>
等)都可以获得history
对象的引用
从 reactjs.org:
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
<Router>
接口一般不用于建筑应用;相反,使用适合给定环境的高级路由组件之一。使用<Router>
接口的常见用例之一是将自定义history
对象与Redux
和MobX
等状态管理库同步。
核心react-router
包可通过npm
安装:
npm install --save react-router
Router
类可以包含在应用文件中:
import { Router } from 'react-router'
下一步是创建一个history
对象,然后将其作为值提供给<Router>
的history
道具:
import createBrowserHistory from 'history/createBrowserHistory';
const customHistory = createBrowserHistory()
这里,history
包中的createBrowserHistory
类用于为浏览器环境创建history
对象。history
软件包包括适用于各种环境的类。
最后一步是用<Router>
组件包装应用的根组件,并呈现应用:
ReactDOM.render(
<Router history={customHistory}>
<App />
</Router>, document.getElementById('root'));
注意,<Router>
组件接受一个history
道具,其值是用createBrowserHistory
创建的history
对象。与<BrowserRouter>
组件类似,<Router>
组件只接受一个子组件,当有多个子组件时抛出错误。
React 允许更改其道具值,并在检测到更改时重新渲染组件。在这种情况下,如果我们试图更改分配给历史属性的值,React Router 会抛出一条警告消息。考虑下面的代码片段:
class App extends Component {
state = {
customHistory: createBrowserHistory()
}
componentDidMount() {
this.setState({
customHistory: createBrowserHistory() });
}
render() {
return (
<Router history={this.state.customHistory}>
<Route
path="/"
render={() => <div> In Home </div>}
/>
</Router>
);
}
}
在上例中,状态属性customHistory
包含history
对象,该对象提供给<Router>
组件。但是,当componentDidMount
生命周期函数中customHistory
的值发生变化时,React Router 抛出警告消息警告:您无法更改<路由>历史记录
react-router
包包括一些核心组件,如前面提到的<Router>
组件。该软件包还包括其他几个组件,这些组件随后由react-router-dom
和react-router-native
软件包中提供的组件使用。react-router
包装出口这些组件:
export MemoryRouter from "./MemoryRouter";
export Prompt from "./Prompt";
export Redirect from "./Redirect";
export Route from "./Route";
export Router from "./Router";
export StaticRouter from "./StaticRouter";
export Switch from "./Switch";
export generatePath from "./generatePath";
export matchPath from "./matchPath";
export withRouter from "./withRouter";
在前面的章节中讨论了这里提到的一些组件。该软件包还提供辅助功能,如generatePath
和matchPath
,以及路由实现,如<MemoryRouter>
和<StaticRouter>
。react-router-dom
和react-router-native
中定义的组件和服务导入这些组件和服务,并包含在各自的包中。
react-router-dom
包提供可在基于浏览器的应用中使用的组件。它声明了对react-router
包的依赖,并导出以下组件:
export BrowserRouter from "./BrowserRouter";
export HashRouter from "./HashRouter";
export Link from "./Link";
export MemoryRouter from "./MemoryRouter";
export NavLink from "./NavLink";
export Prompt from "./Prompt";
export Redirect from "./Redirect";
export Route from "./Route";
export Router from "./Router";
export StaticRouter from "./StaticRouter";
export Switch from "./Switch";
export generatePath from "./generatePath";
export matchPath from "./matchPath";
export withRouter from "./withRouter";
注意这里提到的一些组件也包含在react-router
包中。react-router-dom
中的组件导入react-router
中定义的组件,然后导出。例如,看看<Route>
组件:
import { Route } from "react-router";
export default Route;
路由实现BrowserRouter
、<HashRouter>
和<MemoryRouter>
创建特定于给定环境的history
对象,并呈现<Router>
组件。我们将很快了解这些路由的实现。
react-router-native
包利用react-router
中的<MemoryRouter>
实现,提供<NativeRouter>
接口。NativeRouter
实现及其打包细节将在后面的章节中讨论。
第一章简要讨论了<BrowserRouter>
组件。顾名思义,<BrowserRouter>
组件用于基于浏览器的应用,它使用 HTML5 的历史 API 来保持 UI 与浏览器的 URL 同步。这里,我们来看看组件如何为浏览器环境创建一个history
对象,并将这个history
对象提供给<Router>
。
<BrowserRouter>
组件接受以下道具:
static propTypes = {
basename: PropTypes.string,
forceRefresh: PropTypes.bool,
getUserConfirmation: PropTypes.func,
keyLength: PropTypes.number,
children: PropTypes.node
};
与<Router>
接口类似,<BrowserRouter>
只接受一个子组件(通常是应用的根组件)。前面的代码片段中提到的children
属性指的是这个子节点。使用history
包中的createBrowserHistory
方法创建history
对象,用于初始化<Router>
:
import { createBrowserHistory as createHistory } from "history";
import Router from "./Router";
class BrowserRouter extends React.Component {
...
history = createHistory(this.props);
...
render() {
return <Router
history={this.history}
children={this.props.children}
/>;
}
}
在前面的代码片段中,<BrowserRouter>
使用提供的道具使用history/createBrowserHistory
类创建history
对象。然后组件渲染<Router>
组件,并从道具中提供创建的history
对象和children
对象。
basename
属性用于为应用中的所有位置提供基本 URL 路径。例如,如果要在/admin
路径而不是根路径/
上渲染应用,请在<BrowserRouter>
中指定basename
道具:
<BrowserRouter basename="/admin">
<App />
</BrowerRouter>
basename
道具现在将基本 URL 路径/admin
添加到应用中。当您使用<Link>
和<NavLink>
导航时,basename
路径将添加到 URL 中。例如,考虑下面的代码,带有两个 Ty5T5 组件:
<BrowserRouter basename="/admin">
<div className="component">
<nav>
<Link to="/">Home</Link>
<Link to="/dashboard">Dashboard</Link>
</nav>
</div>
</BrowserRouter>
当您点击Home
链接(路径/
时,您会注意到 URL 路径被更新为/admin
而不是/
。当您点击Dashboard
链接时,更新的 URL 路径为/admin/dashboard
。使用<BrowserRouter>
中的basename
道具,前面的<Link>
组件转换为以下内容:
<a href='/admin'>Home</a>
<a href='/admin/dashboard'>Dashboard</a>
锚链的href
属性以/admin
路径作为前缀。
forceRefresh
属性是一个布尔属性,当设置为true
时,导航到任何路由将导致页面刷新,而不是更新页面的特定部分,整个页面将重新加载:
<BrowserRouter forceRefresh={true}>
<Link to="/dashboard">Dashboard</Link>
</BrowserRouter>
当您点击导航链接Dashboard
时,您会注意到页面在请求 URL 路径/dashboard
时会重新加载。
keyLength
道具用于指定location.key
的长度。locaction.key
属性表示提供给位置的唯一密钥。请看以下代码段:
<BrowserRouter keyLength={10}>
<div className="container">
<nav>
<Link to="/dashboard">Dashboard</Link>
<Link to="/user">User</Link>
</nav>
<Route
path="/dashboard"
render={({ location }) =>
<div> In Dashboard, Location Key: {location.key} </div>
}
/>
<Route
path="/user"
render={({ location }) =>
<div> In User, Location Key: {location.key} </div>
}
/>
</div>
</BrowserRouter>
当您导航到/dashboard
或/user
路径时,location.key
的值将是长度为 10 的随机字母数字字符串。默认情况下,用于生成密钥的keyLength
道具的值为 6。
当您使用导航链接在/dashboard
和/user
路径之间来回导航时,您会注意到每个导航都会生成一个新的键。这是因为如果要使用导航链接进行导航,将调用history.push
并生成一个新密钥,并且该密钥对于历史堆栈中的每个条目都是唯一的。因此,当您通过单击浏览器的后退按钮进行导航时,会调用history.pop
,您会注意到为该位置生成的键会显示出来,而不会生成新键。
getUserConfirmation
道具接受一个函数作为其值,当用户启动的导航被<Prompt>
组件阻止时,它将被执行。<Prompt>
组件使用window.confirm
方法显示一个确认对话框,仅当用户单击“确定”按钮时,才会将用户导航到所选路径。但是,当<BrowserRouter>
组件指定getUserConfirmation
道具时,将执行作为该道具值提供的函数。这提供了显示自定义对话框的机会。
让我们来看一下下面的配置:
<BrowserRouter getUserConfirmation={this.userConfirmationFunc}>
<div className="container">
<nav>
<Link to="/dashboard">Dashboard</Link>
<Link to="/user">User</Link>
</nav>
<Route
path="/dashboard"
render={({ location }) =>
<div> In Dashboard, Location Key: {location.key} </div>
}
/>
<Route
path="/user"
render={({ location }) =>
<div> In User, Location Key: {location.key}
<Prompt message="This is shown in a confirmation
window" />
</div>
}
/>
</div>
</BrowserRouter>
假设当前 URL 路径为/user
,您试图通过点击nav
菜单中提供的导航链接导航到不同的路径,如/dashboard
。如果未指定getUserConfirmation
道具,将显示<Prompt>
消息。在这种情况下,执行在组件类中定义的函数userConfirmationFunc
。
您可以调用window.confirm
显示确认对话框,询问用户导航:
userConfirmationFunc = (message, callback) => {
const status = window.confirm(message);
callback(status); }
该函数接受两个参数-message
和callback
。message
参数指定需要显示的消息,<Prompt>
组件中包含的message
道具提供该值。该函数将执行作为第二个参数提供的回调函数
这里,<BrowserRouter>
提供了一个回调函数作为第二个参数。使用提供的message
调用window.confirm
功能,并向用户显示两个按钮 OK 和 CANCEL;单击确定时,status
设置为真,单击取消时,status
设置为false
。使用此status
值调用作为第二个参数提供的callback
函数;它是允许用户导航到所选路由的真实值。
这是默认行为;在允许用户导航到所选页面之前,将显示本机浏览器确认对话框。但是,这种行为可以在前面提到的userConfirmationFunc
中改变;可以显示自定义对话框,而不是显示浏览器的本机确认对话框。
在本例中,我们添加material-UI
,其中包括一个自定义对话框组件:
npm install --save @material-ui/core
让我们创建一个自定义对话框,将Dialog
组件包装在@material-ui/core
中:
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle
} from '@material-ui/core';
export class ConfirmationDialog extends Component {
render() {
const { message, handleClose, isOpen } = this.props;
return (
<Dialog open={isOpen}>
<DialogTitle>Custom Prompt</DialogTitle>
<DialogContent>{message}</DialogContent>
<DialogActions>
<Button onClick={handleClose.bind(this, true)}>
OK
</Button>
<Button onClick={handleClose.bind(this, false)}>
CANCEL
</Button>
</DialogActions>
</Dialog>
)
}
}
此组件接受三个道具-message
、handleClose
和isOpen
。message
道具是您希望在自定义对话框中显示的消息,handleClose
道具是提供给组件的功能参考,当用户单击按钮 OK 或 CANCEL 时,将调用该组件,该按钮分别允许或取消转换到所选路径。
让我们在根组件文件(在App.js
中)中使用它,并在用户尝试导航到其他路径时显示ConfirmationDialog
:
class App extends Component {
state = {
showConfirmationDialog: false,
message: '',
callback: null
}
...
我们将首先在 React 组件中将state
属性设置为其初始值。当用户尝试导航到不同的路由时,前面提到的state
属性会发生变化:
... userConfirmationFunc = (message, callback) => {
this.setState({
showConfirmationDialog: true,
message: message,
callback: callback
});
}
前面的userConfirmationFunc
函数设置state
属性,以便当用户试图离开当前路由时,它将显示自定义确认对话框(ConfirmationDialog
在App
组件中定义的以下handleClose
功能将提供给我们之前创建的ConfirmationDialog
组件:
...
handleClose(status) {
this.state.callback(status);
this.setState({
showConfirmationDialog: false,
message: '',
callback: null
})
}
这为我们提供了一种隐藏自定义确认对话框并将组件的state
属性重置为初始值的方法。this.state.callback(status)
语句将关闭确认对话框,并将用户导航到所选路由(如果状态为 true)或取消导航(如果状态为 false)
以下是组件类的更新渲染方法:
...
render() {
return (
<BrowserRouter
getUserConfirmation={this.userConfirmationFunc}>
...
<Route
path="/user"
render={({ location }) => {
return (
<div>
In User, Location Key: {location.key}
<Prompt message="This is shown in a
confirmation modal" />
</div>
);
}}
/>
<ConfirmationDialog
isOpen={this.state.showConfirmationDialog}
message={this.state.message}
handleClose={this.handleClose.bind(this)}
/>
...
</BrowserRouter>
)
}
}
在前面的渲染方法中,包含自定义的ConfirmationDialog
框,并且仅当状态属性showConrfirmationDialog
设置为true
时才会渲染。userConfirmationFunc
设置state
属性,自定义对话框如下图所示:
当用户单击确定或取消按钮时,ConfirmDialog
框调用前面代码段中的handleClose
函数。OK 按钮将发送值true
,而 CANCEL 按钮将false
值发送到前面定义的handleClose
功能
<HashRouter>
组件是react-router-dom
包的一部分,与<BrowserRouter>
类似,它也用于构建浏览器环境的应用。<BrowserRouter>
和<HashRouter>
之间的主要区别在于组件创建的 URL:
<BrowserRouter>
创建一个 URL,如下所示:
www.packtpub.com/react-router
而<HashRouter>
在 URL 中添加了一个哈希:
www.packtpub.com/#/react-router
<BrowserRouter>
组件利用 HTML5 历史 API 来跟踪路由历史,而<HashRouter>
组件使用window.location.hash
(URL 的哈希部分)来记住浏览器历史堆栈中的更改。<BrowserRouter>
应该用于构建在支持 HTML5 历史 API 的现代浏览器上工作的应用,而<HashRouter>
应该用于需要支持传统浏览器的应用。
<HashRouter>
使用createHashHistory
类创建history
对象。然后将该history
对象提供给核心<Router>
组件:
import { createHashHistory as createHistory } from "history";
class HashRouter extends React.Component {
...
history = createHistory(this.props);
...
render() {
return <Router
history={this.history}
children={this.props.children}
/>;
}
}
<HashRouter>
接受以下道具:
static propTypes = {
basename: PropTypes.string,
getUserConfirmation: PropTypes.func,
hashType: PropTypes.oneOf(["hashbang", "noslash", "slash"]),
children: PropTypes.node
};
与<BrowserRouter>
类似,道具basename
和getUserConfirmation
分别用于指定基本 URL 路径和确认导航到所选 URL 的功能。但是<HashRouter>
不支持location.key
和location.state
,因此不支持keyLength
道具。此外,不支持道具forceRefresh
。
让我们来看看这个例子。
hashType
属性用于指定window.location.hash
使用的编码方法。可能的值为slash
、noslash
和hashbang
。
让我们来看看当包含了这些值中的一个值时,URL 是如何形成的:
<HashRouter hashType="slash">
<App />
</HashRouter>
当您指定slash
作为hashType
属性的值时,会在散列(#
之后添加斜杠(/
。因此,URL 将采用以下形式-#/
、#/dashboard
、#/user
等等。
Please note, slash
is the default value for the prop hashType
, and it's not required to include the hashType
prop when you want to add a slash after the #
.
类似地,当hashType
prop 的值为noslash
时,URL 的形式为#
、#dashboard
、#user
等:
<HashRouter hashType="noslash">
当值hashbang
被分配给hashType
道具时,它会创建格式为#!/
、#!/dashboard
、#!/user
等的 URL:
<HashRouter hashType="hashbang">
The hashbang
was added so that the search engine bots can crawl and index single-page application. However, Google has deprecated this crawling strategy. Read about it here: https://webmasters.googleblog.com/2015/10/deprecating-our-ajax-crawling-scheme.html.
react-router
包中的<Router>
组件提供路由接口的底层实现。react-router-dom
和react-router-native
中的各种路由使用此低级<Router>
接口为给定环境提供路由功能。<Router>
中的history
道具用于指定给定环境的history
对象。例如,<BrowserRouter>
组件使用history/createBrowserHistory
在浏览器环境中创建history
对象。所有路由组件只接受一个子组件,它通常是应用的根组件。
react-router-dom
中的BrowserRouter
组件利用 HTML5 历史 API 使应用的 URL 与浏览器的历史保持同步。它接受道具-basename
、keyLength
、forceRefresh
和getUserConfirmation
。另一方面,<HashRouter>
在浏览器的 URL 中添加一个散列(#),并使用window.location.hash
跟踪历史。它接受道具basename
、getUserConfirmation
和hashType
。hashType
属性用于指定window.location.hash
使用的编码方式;可能的值为slash
、noslash
和hashbang
。
在第 6 章中在服务器端呈现的 React 应用中使用 StaticRouter,我们将了解使用<StaticRouter>
组件的服务器端呈现。