Skip to content

Latest commit

 

History

History
519 lines (394 loc) · 22.1 KB

File metadata and controls

519 lines (394 loc) · 22.1 KB

五、了解核心路由,配置BrowserRouterHashRouter组件

React Router 库提供了几个组件来处理各种用例,例如添加带有<Link><NavLink>的导航链接,使用<Redirect>组件重定向用户,等等。<BrowserRouter>组件包装应用的根组件(<App />,并使这些组件能够与history对象交互。当应用初始化时,<BrowserRouter>组件初始化history对象,并使用 React 的context将其提供给所有子组件。

单页应用中的路由不是真正的路由;相反,它是组件的条件呈现。<BrowserRouter>组件创建history对象,history对象有pushreplacepop等方法,在导航时使用。当用户在页面之间导航时,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>组件接受两个道具—historychildrenhistory对象可以是浏览器历史记录的引用,也可以是内存中维护的应用历史记录(这在浏览器历史记录实例不可用的本机应用中很有用)。<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对象与ReduxMobX等状态管理库同步。

包括来自 react 路由的

核心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-domreact-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";

在前面的章节中讨论了这里提到的一些组件。该软件包还提供辅助功能,如generatePathmatchPath,以及路由实现,如<MemoryRouter><StaticRouter>react-router-domreact-router-native中定义的组件和服务导入这些组件和服务,并包含在各自的包中。

反应路由 dom 包

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 道具

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); }

该函数接受两个参数-messagecallbackmessage参数指定需要显示的消息,<Prompt>组件中包含的message道具提供该值。该函数将执行作为第二个参数提供的回调函数

这里,<BrowserRouter>提供了一个回调函数作为第二个参数。使用提供的message调用window.confirm功能,并向用户显示两个按钮 OK 和 CANCEL;单击确定时,status设置为真,单击取消时,status设置为false。使用此status值调用作为第二个参数提供的callback函数;它是允许用户导航到所选路由的真实值。

这是默认行为;在允许用户导航到所选页面之前,将显示本机浏览器确认对话框。但是,这种行为可以在前面提到的userConfirmationFunc中改变;可以显示自定义对话框,而不是显示浏览器的本机确认对话框。

使用 getUserConfirmation 属性显示自定义对话框

在本例中,我们添加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>
        )
    }
}

此组件接受三个道具-messagehandleCloseisOpenmessage道具是您希望在自定义对话框中显示的消息,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>类似,道具basenamegetUserConfirmation分别用于指定基本 URL 路径和确认导航到所选 URL 的功能。但是<HashRouter>不支持location.keylocation.state,因此不支持keyLength道具。此外,不支持道具forceRefresh

让我们来看看这个例子。

散列类型道具

hashType属性用于指定window.location.hash使用的编码方法。可能的值为slashnoslashhashbang

让我们来看看当包含了这些值中的一个值时,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-domreact-router-native中的各种路由使用此低级<Router>接口为给定环境提供路由功能。<Router>中的history道具用于指定给定环境的history对象。例如,<BrowserRouter>组件使用history/createBrowserHistory在浏览器环境中创建history对象。所有路由组件只接受一个子组件,它通常是应用的根组件。

react-router-dom中的BrowserRouter组件利用 HTML5 历史 API 使应用的 URL 与浏览器的历史保持同步。它接受道具-basenamekeyLengthforceRefreshgetUserConfirmation。另一方面,<HashRouter>在浏览器的 URL 中添加一个散列(#),并使用window.location.hash跟踪历史。它接受道具basenamegetUserConfirmationhashTypehashType属性用于指定window.location.hash使用的编码方式;可能的值为slashnoslashhashbang

第 6 章在服务器端呈现的 React 应用中使用 StaticRouter,我们将了解使用<StaticRouter>组件的服务器端呈现。