[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内进行切换渲染

整个项目目录如下:

路由配置

在spa.es6中,我们通过react-router配置路由关系,首先<Router>标签写在最外围,有一个history属性指定了基于浏览器的#进行路由。

在<Route>父标签内指定了使用SpaApp组件作为根容器,在2个<Route>子标签中指定了2个子路径,一个是/comp1指向Component1组件,一个是/comp2指向Component2组件,可以互相切换。整个spa.es6代码如下:

这里IndexRoute的意义是:如果直接访问路径/,SpaApp主容器不知道填充哪个子组件,所以需要配置一个默认的组件(这里是Component1),其意义对于一般App来说就是App启动后的首页。

那么看一下SpaApp主容器的实现,它通过{this.props.children}引入的就是某个子路由对应组件的jsx,在这里项目里可能是Component1或者Component2,先看一下代码:

这里我特意为<div>加了一个id属性,下面我们看一下渲染后的页面结构,是可以找到这个div的。

首先访问localhost:8080,这相当于访问了整个app的路径/:

可以看到在<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

Component2.es6

访问localhost:8080,点击链接可以实现来回跳转。页面结构并不复杂,只是<a>标签href变更了地址里的#部分,而react-router会监听#的变化(浏览器事件)并作出对应的处理。

控制浏览器历史

单页应用一般在手机上访问,或者在微信等平台中嵌入,所以没有PC浏览器上的地址栏,刷新按钮,退后和前进按钮。因此,为了追求native的体验,一般会在App内设置独立的后退按钮。

这里可以学习一下react-router的原理:点我,react-router官方API文档:点我

我给Component2添加一个button,并给button绑定了onClick事件,当点击时执行后退操作。这里的后退其实就和浏览器的后退没有什么差别,重要的是:后退会导致重新渲染,也就是说从component2回退到component1会重新component1对象重新构造,重新渲染,之前保存的状态都不在了!

代码如下:

this.context.router是react-router赋予我们的一个对象,我不深究原理。该对象有一系列操作历史记录的函数,这里用的goBack方法可以发起浏览器回退。比较重要的是:为了访问router对象,我们需要主动给类赋予静态变量contextTypes,其中key是对象的名字,value是一个函数,返回router的类型。记住,这是固定写法,暂时不需要太关注原因!

现在通过点击按钮,就可以实现浏览器回退功能了,这和点击浏览器的后退按钮貌似没有区别,不过要知道router还提供了若干操作历史栈的函数,可以后面随着项目慢慢挖掘。

通配和参数

react-router支持路由规则通配,详细的内容可以继续学习:《阮一峰的React Router 使用教程》。这里我只体验一下通配捕获参数以及获取URL参数,直接看例子吧。

首先修改spa.es6中的路由规则,设置支持/comp1(/:info)这个路由项,并且捕获括号中的内容:

其次,修改Component1.es6中的构造函数,取出相关的信息:

这里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,到时再说吧~

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~