[学习笔记] Cordova+AmazeUI+React 做个通讯录 系列文章
目录
- 准备
- 联系人列表(1)
- 联系人列表(2)
- 联系人详情
- 单页应用 (With Router)
- 使用 SQLite
传送门:全部章节 示例代码
前面实现了联系人列表和详情两个页面,并通过点击事件和返回按钮处理了两个页面之间有切换。但同时引起一个疑问:为什么不是单页程序?
React 的出现不是为了单页应用,但在很多时候用于单页应用。由于其组件化的设计,React 也的确很容易写单页应用。然而说到单页应用,就不得不提到 router,这个曾经只是在服务端使用的名词被单页应用带到了前端。
- router,路由器,路由处理器
- route,路由
单页应用路由的原理和作用
大家都知道,URL 改变会触发浏览器跳转页面——除了一种情况:只改变 #
后面的部分,因为 #
后面的部分是由浏览器为自己设计的跳转标记,连同 #
号一起被称为 hash。它标识了当前页面内部的一个位置,这个位置可能是由 标记的,也有可能是标签中的
id
属性标记的。
关于 hash,可以参阅 阮一峰 URL的井号
现代浏览器中,hash 变化会增加访问历史,也会触发相应的事件。但无论如何,hash 变化默认情况都不会向服务器请求数据。因此路由的设计就利用 hash 的特点,通过 hash 的变化来改变当前页面的布局,再利用 AJAX 等技术获取新页面布局所需要的后端数据,完成页面的更新。
由此看来,路由处理器的作用其实是在一定程度上代替了浏览器对 URL 的处理,将由 URL 变化产生的整页更新改变为由 hash 改变而触发局部更新。React 的设计在局部更新这个问题进行了非常优秀的处理,尤其是大大增加了其处理效率。因此 React 非常适合用于单页 Web 应用。
抄一个 router
还记得早前提到的 Sample Mobile Application with React and Cordova 么,在它的 Iteration 5 就提到了 路由处理(Routing),而在其示例代码中也出现了一个新的脚本:router.js。
router 处理的入口通常是 window.onhashchange
事件。在 router.js 中,return 之前就有一句
window.onhashchange = start;
所以主要的处理函数是 function start() {...}
。在 start 函数中,最外层循环是在 routes
中循环,而 routes
数组中的内容是由 addRoute()
添加的。所以基本上可以了解这个简易 router 的处理过程:
- 配置阶段使用
router.addRoute()
添加路由及其对应的处理函数 - 在
window.onhashchange
的时候从当前 url 中取得 hash 并与配置好的路由进行比较,找到合适的路由,执行其处理函数
仔细分析 start()
中的循环可以发现路由处理的一些细节,不过直接看 app.js 中配置 router 的部分可以更快明白这个简易 router 的用法。
在通讯录中使用路由
通讯录现在是由两页完成,index.html 和 detail.html,在使用路由就需要将这两页合并在一起。幸好这两个页面只有一句话不同,只需要将 detail.html 中的 移到 index.html 中就可以完成合并。
之后可以删除 detail.html。但这样的合并只是第一步。这个时候看到的效果已经不是通讯录列表了,而是“查无此人”。Why?因为 index.jsx 和 detai.jsx 都有 React.render()
语句对 document.body
的内容进行重绘,最后执行的一句覆盖了之前的一句。这也是为什么 Sample Mobile Application with React and Cordova 的 app.js 中,路由处理函数可以起作用的原因。
把页面组件化
要把两个独立页面合并到一个页面用,并通过路由来控制显示,那就很有必要把原来的页面组件化——哦,原来的页面本来就是以组件方式定义的,只不过是作为根组件渲染的。不过原来并没有考虑到会在同一个运行上下文中使用两个页面,所以它们的名字都叫 Page。是时候改个名字:一个叫 IndexPage,一个叫 DetailPage 就挺好。
每个页面组件都使用了一些其它的自定义组件,而这些组件不会被另一个页面组件用到,所以可以对这些组件进行一个私有化封装。就像这样
var IndexPage = (function(A) {
var Person = React.createClass({ ... });
return React.createClass({ ... });
})(AMUIReact);
var DetailPage = (function(A) {
var detailBase = { ... };
var DetailItem = React.createClass({ ... });
var DetailLinkItem = React.createClass({ ... });
var Detail = React.createClass({ ... });
return React.createClass({ ... });
})(AMUIReact);
新的渲染入口
组件化 IndexPage 和 DetailPage 的时候删除了两个 jsx 中的 React.render(...)
,所以还需要一个渲染的入口,不妨加一个 app.jsx:
router.addRoute("", function() {
React.render( , document.body);
});
router.addRoute(":id", function() {
React.render( , document.body);
});
router.start();
相应的, index.jsx 中跳转到详情的链接也要从 "detail.html#" + this.props.id
改为 "#" + this.props.id
。
由于添加了 router.js
和 app.jsx
,index.html 中引用脚本的部分也需要做一些调整
router.js 的位置只需要在 app.jsx 之前就行。这里把它当作一个库来引用,所以放在最前面。
用别人的轮子:React Router
在抄 router 的时候,我就猜想,如果 router 是一个常用的功能,那就一定已经存在现成的库,即使不是 React 官方的,也会有第 3 方的出现。结果使用“react router”作为关键字一搜,就搜到了 React Router。然后参考了 再谈 React Router 使用方法 和 React Router 簡介 两篇文章之后,开始着手修改。
ReactRouter.js
在 React Router 的官网及各种文章中都看到这样的示例
var Router = require("react-router");
这很明显是 node.js 的语法。难道 React Router 不是用于前端的?似乎不太可能啊!
终于在 React Router 的 README.md 中发现它提到了 CDN
If you just want to drop a