React:单页面路由配置

本文介绍如何使用react-router库对单页面应用进行路由配置。

前言

react-router是由React官方维护的路由库,它本身也是一个React组件。通过切换不同的Route对象来动态加载页面上的components,达到切换页面的效果。编写本文时,react-router已更新至5.2.0版本。使用如下命令进行安装:

npm install --save react-router-dom

基本用法

react-router提供了多种Router对象:<BrowserRouter> , <HashRouter>, <MemoryRouter>, <StaticRouter><NativeRouter>。我们以官方推荐的BrowserRouter作为本文的例子。实际实现时一般会使用以上五种高阶Router之一,但原理并无大的差异。它们的的对比将在稍后介绍。
和其他的React元件一样,BrowserRouter需要在js中引入。使用时需要用其包裹页面的其他组件。例如:

import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter} from 'react-router-dom';

const App = () => {return <h1>Hello World!</h1>}

ReactDOM.render(
    <BrowserRouter>
        <App />
    </BrowserRouter>, 
document.getElementById("root")
);

– Link和Route对象

<Route>对象包含:path属性,用于匹配URL;component属性,用来指定URL匹配时需要被渲染的元素;exact属性用来指定是否严格匹配URL。举例如下:

<BrowserRouter>
    <Route exact path="/" component={App} />
    <Route path="/index" component={Index}> 
</BrowserRouter>

当用户访问根路径时,App组件将会被加载,当用户访问/index目录时,IndexPage将会被加载。
注:如果不在跟路径路由添加exact属性,则访问/index路径时App元件也会被加载。

– Switch对象

React中的<Switch>和C语言中的switch用法类似,时它用来切换到第一个匹配的路由分支,其他的路由分支不会被渲染。当多个<Route>对象被放置在同一层级时,我们使用<Switch>元件来将其包裹。例如:

<BrowserRouter>
    <Switch>
        <Route exact path="/" component={App} />
        <Route path="/index" component={Index}>
    </Switch>
</BrowserRouter>

当使用<Switch>时,如果我们在第一条路由不指定exact属性的话,那么无论我们访问"/", "/index", "/anything",Router都将匹配到上段代码的第一条路由,而不渲染其他组件。

– 在Router中使用history

引用官方文档的话:“React Router 是建立在 history 之上的。 简而言之,一个 history 知道如何去监听浏览器地址栏的变化, 并解析这个 URL 转化为 location 对象, 然后 router 使用它匹配到路由,最后正确地渲染对应的组件。“

自react-router v4以来,官方推荐使用的<BrowserRouter>已经在内部配置好了browserHistory,我们无需进行额外配置。有关History的底层实现,可以参照这篇博文:React路由之BrowserHistory实现原理

– 在其他元件中进行导航

当我们想在其他元件中创建超链接时,只需要使用<Link>或者<NavLink>对象即可完成导航。后者是前者的特殊形式,用于在链接被选中时向其添加style样式。它们最终被渲染为HTML中的<a>标签,只是跳转方式略有不同。例如:

<Link to="/">Back To Home</Link>
<Link to={{
pathname: "/",
search: "/?id=0",
hash: "#hash location",
state: {jumpBack: true}
}}>Back To Home</Link>

如以上代码,to属性接受字符串或者location类型作为参数。当用户点击链接时,path, search, hash会被合并为href。大致流程时React会调用history.pushState方法进行URL跳转并将state传入location对象,然后触发history中的事件监听器,完成对历史记录的更新。

– 嵌套路由

假设我们的/index路径下有多个子页面,例如/index/01, /index/02等。我们想通过IndexPage内的超链接来访问这两个页面,应当如何在IndexPage元件里配置子路由?我们现有的index.js如下:

import...

function Index() { 
    return ( 
            <div> 
                <ul> 
                    <li> IndexPage01</li> 
                    <li> IndexPage02</li> 
                </ul>
            </div>
    );
};

在当前的组件里创建<Router>,并在<Route>里写死url固然是一种办法,但这样的写法会增加以后debug和修改的难度。为了避免这种写法,react-router在创建<Route>元件内所渲染的元素时,会向其props传入match参数,这个参数指的就是前一条路由match的路径。由此,我们便可以将当前组件的Router写的更灵活,具体如下:

import...

function Index(props) { 
    return ( 
            <div> 
                <ul> 
                    <li> <Link to=${props.match.url}/01>IndexPage01</Link></li> 
                    <li> <Link to=${props.match.url}/02>IndexPage02</Link></li> 
                </ul>
            </div>
            <BrowserRouter>
                <Route path=${props.match.url}/01 component={IndexPage01}/>
                <Route path=${props.match.url}/02 component={IndexPage02}/>
            </BrowserRouter>
    );
};

match中的pathurl参数也可以通过调用let {path, url} = useRouteMatch()来获取。