[React-章节5]学习react-router,实现单页应用demo
接上篇《webpack的代码分割与公共代码提取》,本篇终于要学习如何编写单页面应用了。在多页面应用中,超链接<a>会跳转到另外一个html中,而单页面应用中,超链接<a>不会发生html页面跳转,而是触发在当前页面中渲染不同的组件,这是最直观的差别。
基于webpack+react实现单页应用,需要用到react-router插件,下面就开始学习吧!
环境准备
- 准备新的项目环境,本篇博客完整的项目代码可以点我下载。
- 安装react-router:npm install react-router –save
开始学习
文档
基于react-router官方doc学习,先从基础basic部分学起。也可以详细的看一下《阮一峰的React Router 使用教程》。
项目结构
- 整个项目是单页的index.html,相关的webpack.config.js已作更新
- spa.es6作为项目entry入口,仅配置react-router组件的主体容器和路由映射关系即可
- SpaApp.es6相当于一个App的主体容器,App的不同页面在容器内变换
- Component1和Component2相当于App的2个页面,通过路由可以实现在SpaApp.es6内进行切换渲染
整个项目目录如下:
1 2 3 4 5 6 7 8 |
-rw-r--r-- 1 baidu staff 281 9 20 13:50 Component1.es6 -rw-r--r-- 1 baidu staff 281 9 20 13:57 Component2.es6 -rw-r--r-- 1 baidu staff 295 9 20 14:46 SpaApp.es6 drwxr-xr-x 229 baidu staff 7786 9 20 14:11 node_modules -rw-r--r-- 1 baidu staff 897 9 20 14:10 package.json -rw-r--r-- 1 baidu staff 558 9 20 14:44 spa.es6 -rw-r--r-- 1 baidu staff 296 9 20 13:43 webpack-production.config.js -rw-r--r-- 1 baidu staff 1623 9 20 13:57 webpack.config.js |
路由配置
在spa.es6中,我们通过react-router配置路由关系,首先<Router>标签写在最外围,有一个history属性指定了基于浏览器的#进行路由。
在<Route>父标签内指定了使用SpaApp组件作为根容器,在2个<Route>子标签中指定了2个子路径,一个是/comp1指向Component1组件,一个是/comp2指向Component2组件,可以互相切换。整个spa.es6代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import React from "react"; import ReactDOM from "react-dom"; import {Router, Route, IndexRoute, hashHistory} from "react-router"; import SpaApp from "./SpaApp"; import Component1 from "./Component1"; import Component2 from "./Component2"; ReactDOM.render( ( <Router history={hashHistory}> <Route path="/" component={SpaApp}> <IndexRoute component={Component1} /> <Route path="comp1" component={Component1}/> <Route path="comp2" component={Component2}/> </Route> </Router> ), document.body ); |
这里IndexRoute的意义是:如果直接访问路径/,SpaApp主容器不知道填充哪个子组件,所以需要配置一个默认的组件(这里是Component1),其意义对于一般App来说就是App启动后的首页。
那么看一下SpaApp主容器的实现,它通过{this.props.children}引入的就是某个子路由对应组件的jsx,在这里项目里可能是Component1或者Component2,先看一下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import React from "react"; export default class SpaApp extends React.Component { constructor(props, context) { super(props, context); } render() { return ( <div id="thisIsRoot"> {this.props.children} </div> ); } } |
这里我特意为<div>加了一个id属性,下面我们看一下渲染后的页面结构,是可以找到这个div的。
首先访问localhost:8080,这相当于访问了整个app的路径/:
1 |
<div data-reactroot="" id="thisIsRoot"><div><h1>Component1</h1></div></div> |
可以看到在<div id=”thisIsRoot”>里渲染了完整的Component1组件,其实访问localhost:8080相当于访问了localhost:8080/index.html#/。
这里/index.html没有什么疑问,我们是单页面应用,因此必然要有一个html作为载体从而执行js渲染。而#则用于react-router实现路由功能,#之后的路径就对应了我们通过<router>配置的一系列路由路径,访问对应的路径就可以渲染出不同的组件,并不复杂。
而index.html作为http服务器默认加载的文件,我们通常可以忽略。因此我们可以推断,访问localhost:8080#/comp1可以展现Component1,而localhost:8080#/comp2可以展现Component2,可以自己试验一下。
路由跳转
上面配置好了路由映射,但是只能通过修改浏览器的#路径去切换页面。下面,通过react-router的<Link>标签,可以实现路由跳转,从而实现组件的切换。
我为Component1组件添加一个链接,从而跳转到Component2组件,同样Component2组件也包含了Component1组件的跳转链接,下面分别看一下代码:
Component1.es6
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import React from "react"; import {Link} from "react-router"; export default class Component1 extends React.Component { constructor(props, context) { super(props, context); } render() { return ( <div> <h1>Component1</h1> <Link to="/comp2">跳转Component2</Link> </div> ); } } |
Component2.es6
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import React from "react"; import {Link} from "react-router"; export default class Component1 extends React.Component { constructor(props, context) { super(props, context); } render() { return ( <div> <h1>Component2</h1> <Link to="/comp1">跳转Component1</Link> </div> ); } } |
访问localhost:8080,点击链接可以实现来回跳转。页面结构并不复杂,只是<a>标签href变更了地址里的#部分,而react-router会监听#的变化(浏览器事件)并作出对应的处理。
1 |
<div data-reactroot="" id="thisIsRoot"><div><h1>Component2</h1><a href="#/comp1">跳转Component1</a></div></div> |
控制浏览器历史
单页应用一般在手机上访问,或者在微信等平台中嵌入,所以没有PC浏览器上的地址栏,刷新按钮,退后和前进按钮。因此,为了追求native的体验,一般会在App内设置独立的后退按钮。
这里可以学习一下react-router的原理:点我,react-router官方API文档:点我。
我给Component2添加一个button,并给button绑定了onClick事件,当点击时执行后退操作。这里的后退其实就和浏览器的后退没有什么差别,重要的是:后退会导致重新渲染,也就是说从component2回退到component1会重新component1对象重新构造,重新渲染,之前保存的状态都不在了!
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import React from "react"; import {Link} from "react-router"; export default class Component1 extends React.Component { constructor(props, context) { super(props, context); } render() { return ( <div> <h1>Component2</h1> <Link to="/comp1">跳转Component1</Link> <button onClick={this.context.router.goBack}>我就是浏览器后退按钮</button> </div> ); } } Component1.contextTypes = { router: () => { React.PropTypes.object.isRequired } }; |
this.context.router是react-router赋予我们的一个对象,我不深究原理。该对象有一系列操作历史记录的函数,这里用的goBack方法可以发起浏览器回退。比较重要的是:为了访问router对象,我们需要主动给类赋予静态变量contextTypes,其中key是对象的名字,value是一个函数,返回router的类型。记住,这是固定写法,暂时不需要太关注原因!
现在通过点击按钮,就可以实现浏览器回退功能了,这和点击浏览器的后退按钮貌似没有区别,不过要知道router还提供了若干操作历史栈的函数,可以后面随着项目慢慢挖掘。
通配和参数
react-router支持路由规则通配,详细的内容可以继续学习:《阮一峰的React Router 使用教程》。这里我只体验一下通配捕获参数以及获取URL参数,直接看例子吧。
首先修改spa.es6中的路由规则,设置支持/comp1(/:info)这个路由项,并且捕获括号中的内容:
1 2 3 4 5 6 7 |
<Router history={hashHistory}> <Route path="/" component={SpaApp}> <IndexRoute component={Component1} /> <Route path="comp1(/:info)" component={Component1}/> <Route path="comp2" component={Component2}/> </Route> </Router> |
其次,修改Component1.es6中的构造函数,取出相关的信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import React from "react"; import {Link} from "react-router"; export default class Component1 extends React.Component { constructor(props, context) { super(props, context); console.log(this.props.params.info); console.log(this.props.location.query.detail); } render() { return ( <div> <h1>Component1</h1> <Link to="/comp2">跳转Component2</Link> </div> ); } } |
这里this.props.params.info是获取<route>捕获的:info参数,而this.props.location.query.detail是获取#之后”?detail=xxxx”中的xxxx内容,另外params和query都是普通js对象,params包含所有捕获的参数,而query包含所有querystring参数。
现在打开浏览器,访问:http://localhost:8080/#/comp1/notify?detail=hello+world&_k=5wiwsn。可以看到,命令行打印了notify和hello world,符合预期。个人认为一般是用不到捕获参数的,query参数可以满足普通需求,以后慢慢积累吧。
通过本篇博客,我快速并且初步的掌握了react-router的基本用法,感觉可以动手去做一个小的留言板程序,整体感受一下react生态的设计和编程思路。
另外,goBack()回退会导致组件重新生成,而不会保留组件对象之前的内部状态。这会导致不必要的网络请求和影响用户体验,如果想做到回退不刷新,可能就面临学习redux这种全局状态维护的插件了,通过本地持久化避免后退时的重复拉取。
在下一篇中,我也许会简单了解redux,也许会动手写一个功能明确的简单demo,到时再说吧~
如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~

One thought on “[React-章节5]学习react-router,实现单页应用demo”