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-routerRouter类可以包含在应用文件中:
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 #.
类似地,当hashTypeprop 的值为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>组件的服务器端呈现。
