现实中,应用往往包含很多功能,这些功能无法通过一个页面展示,所以应用往往是‘多页面应用’。而且,用户在这些页面之间来回切换,开发者要做的就是保证用户的操作顺畅。最好的解决办法就是虽然逻辑上是‘多页面应用’,但是页面之间的切换并不引起页面刷新,实际上是‘单页面应用’。
1. 传统的多页面实现方式
如果使用传统的多页面实现方式,那么每次页面切换都是一次网页刷新,每次页面切换都遵循以下步骤:
- 浏览器的地址栏发生变化指向新的URL,于是浏览器发起一个
http
请求到服务器获取页面完整的HTML。 - 浏览器获取到HTML内容后,解析HTML内容。
- 浏览器根据解析的HTML内容确定还需要下载哪些其他资源,包括
javascript
,css
等。 - 浏览器会根据HTML和其他资源渲染页面内容,然后等待用户的其他操作。
2. React-Router
react-router
库可以帮助我们创建react
单页面应用。在学习react-router
之前,我们首先要了解几个概念。
每个URL都包含域名部分和路径(path)部分,例如对于URL http://localhost:3000/home
来说,路径部分是home
,因为应用可能被部署在任何一个域名上,所以决定一个URL显示什么内容的只有路径部分,和域名和端口没有关系。根据路径找对应应用内容的过程,就是react-router
的重要功能——路由。
2.1 路由
react-router
提供了两个组件来完成路由功能,一个是Router
,另一个是Route
。前者Router
在整个应用中只需要一个实例,代表整个路由器。后者Route
则代表每一个路径对应页面的路由规则,一个应用中应该会有多个Route
实例。
我们来用create-react-app
构建一个简单的单页面应用。只包含三个界面,一个是代表主页的Home
,对应的路径是home
。一个是代表详情页的Detail
,对应路径是detail
。最后一个是提示用户资源不存在的NotFound
。
home.js
const Home=()=>{
return (
<div>Home</div>
)
}
detail.js
和notfound.js
代码同上。
react-router
认为每个界面都是一个react
组件,当然这个组件也可以包含很多子组件来构成一个复杂的页面。
当准备好三个界面之后,我们来定义一下路由规则:
Routes.js
import React from 'react';
import {Router,Route,browserHistory} from 'react-router';
import Home from './pages/Home.js';
import Detail from './pages/Detail.js';
import NotFound from './pages/NotFound.js';
const history = browserHistory;
const Routes=()=>(
<Router history={browserHistory}>
<Route path='home' component={Home}/>
<Route path='detail' component={Detail}/>
<Route path='*' component={NotFound}/>
</Router>
)
Router
实例包含三个Route
子组件,分别完成三个路由规则,路径home
被映射到Home
组件,路径detail
被映射到Detail
组件,一个*
通配符代表的是所有路径,被映射到NotFound
组件。
注意:这里的*
通配符这个Route
实例必须要放在最后。因为react-router
是按照Route
在代码中的先后顺序决定匹配的顺序,如果把含有*
通配符的Route
组件放在最前面,那它对任何路径都是匹配成功的,后面的Route
规则根本不会有机会匹配。那样所有的路径都会被映射到NotFound
组件上。
最后,修改一下src/index.js
:
import React from 'react';
import ReactDOM from 'react-dom';
import Routes from './Routes.js';
ReactDOM.render(
<Routes />,
document.getElementById('root')
);
1.2 路由链接和嵌套
如果能在这个单页应用中增加一个顶栏,包含所有页面的链接,这样只要所有的页面都包含这个顶栏,那么就可以方便的进行不同页面的切换了。
react-router
提供了一个名为Link
的组件来支持路由链接,Link
的作用是产生HTML的链接元素,但是对这个链接元素的点击操作并不引起网页跳转,而是被Link
截获操作,将目标路径发送给Router
路由器,这样Router
就知道让哪个Route
下的组件显示了。
src/TopMenu/index.js
import React from 'react';
import {Link} from 'react-router';
const view =()=>{
return (
<div>
<ul>
<li style={liStyle}><Link to='/home'>Home</Link></li>
<li style={liStyle}><Link to='/detail'>Detail</Link></li>
</ul>
</div>
)
}
Link
组件的to
属性指向一个路径,对应路径在src/Routes.js
中有定义。在这里,两个Link
分别指向home
和detail
。注意路径前面有一个'/'
符号,代表从根路径开始匹配。
与此同时,react-router
提供了嵌套功能,避免我们需要针对每个Route
组件增加顶部导航栏组件。
src/app.js
import React from 'react';
import {view as TopMenu} from './TopMenu';
const App=({children})=>{
return (
<div>
<TopMenu />
<div>{children}</div>
</div>
)
}
export default App;
上面的代码虽然看不见Home
,Detail
和NotFound
,不过它们都作为App
的子组件。children
代表就是App
的子组件,所以App
的工作其实就是给其他页面增加一个TopMenu
组件。
src/Routes.js
const Routes =()=>(
<Router history={browserHistory}>
<Route path='/' component={App}>
<Route path='home' component={Home}/>
<Route path='detail' component={Detail}/>
<Route path='*' component={NotFound}/>
</Route>
</Router>
)
现在,假如在浏览器中访问http://localhost:3000/home
,那么react-router
在做路径匹配时,会根据'/home'
中的'/'
找到App
组件,然后根据剩下来的home
找到component
为Home
的Route
。
然后,react-router
会渲染外层Route
相关的组件,但是会把内存Route
的组件作为children
属性传递给外层组件。所以,在渲染App
组件的同时,渲染children
属性也就把Home
组件渲染出来了。最终,TopMenu
和Home
组件都显示在了页面上。
1.3 默认链接
当路径为空时,应用也应该显示有意义的内容,通常对应主页内容。例如Home
组件。
react-router
提供了另外一个组件IndexRoute
,就和传统的index.html
是一个路径下的默认页面一样,IndexRoute
代表一个Route
下的默认路由。
代码如下所示:
src/Routes.js
const Routes =()=>(
<Router history={browserHistory}>
<Route path='/' component={App}>
<IndexRoute component={Home}/>
<Route path='home' component={Home}/>
<Route path='detail' component={Detail}/>
<Route path='*' component={NotFound}/>
</Route>
</Router>
)