[学习笔记] Cordova+AmazeUI+React 做个通讯录 - 单页应用 (With Router)
[學習筆記] Cordova+AmazeUI+React 做個通訊錄 系列文章
目錄
傳送門:全部章節 示例代碼
前面實現了聯系人列表和詳情兩個頁面,并通過點擊事件和返回按鈕處理了兩個頁面之間有切換。但同時引起一個疑問:為什么不是單頁程序?
React 的出現不是為了單頁應用,但在很多時候用于單頁應用。由于其組件化的設計,React 也的確很容易寫單頁應用。然而說到單頁應用,就不得不提到 router,這個曾經只是在服務端使用的名詞被單頁應用帶到了前端。
- router,路由器,路由處理器
- route,路由
單頁應用路由的原理和作用
大家都知道,URL 改變會觸發瀏覽器跳轉頁面——除了一種情況:只改變 # 后面的部分,因為 # 后面的部分是由瀏覽器為自己設計的跳轉標記,連同 # 號一起被稱為 hash。它標識了當前頁面內部的一個位置,這個位置可能是由 <a name="...."> 標記的,也有可能是標簽中的 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 的處理過程:
仔細分析 start() 中的循環可以發現路由處理的一些細節,不過直接看 app.js 中配置 router 的部分可以更快明白這個簡易 router 的用法。
在通訊錄中使用路由
通訊錄現在是由兩頁完成,index.html 和 detail.html,在使用路由就需要將這兩頁合并在一起。幸好這兩個頁面只有一句話不同,只需要將 detail.html 中的 <script type="text/jsx" src="js/detail.jsx"></script> 移到 index.html 中就可以完成合并。
<script type="text/jsx" src="js/index.jsx"></script><script type="text/jsx" src="js/detail.jsx"></script>之后可以刪除 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(<IndexPage />, document.body); });router.addRoute(":id", function() {React.render(<DetailPage />, document.body); });router.start();相應的, index.jsx 中跳轉到詳情的鏈接也要從 "detail.html#" + this.props.id 改為 "#" + this.props.id。
由于添加了 router.js 和 app.jsx,index.html 中引用腳本的部分也需要做一些調整
<script src="js/router.js"></script><script type="text/jsx" src="js/index.jsx"></script><script type="text/jsx" src="js/detail.jsx"></script><script type="text/jsx" src="js/app.jsx"></script>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 <script> tag in your page and be done with it, you can use the UMD/global build hosted on cdnjs.
既然有 CDN,那應該是可以在前端使用的,但是從源碼包沒有發現直接可用的 js 文件,只好按照 README.md 的步驟先 npm install react-router 從 NPM 下載一個下來。果然找到了 UMD build 文件:ReactRouter.js 和 ReactRouter.min.js,把這兩個文件和 CDN 上的一比較,一模一樣。這下放心了。
UMD(Universal Module Definition) 是 AMD 和 CommonJS 的糅合。UMD 先判斷是否支持 Node.js 模塊(即 exports 是否存在),存在則使用 Node.js 模式。再判斷是否支持 AMD(define 是否存在),存在則使用 AMD 方式加載。
如果不使用 CommonJS,也不使用 AMD,React Router 會掛在 global 對象上,即 window.ReactRouter。
React Router 要點
修改 app.jsx
因為不想多加一個腳本文件,所以準備把定義 Main 組件和處理路由配置都放在 app.jsx 中進行。
首先是定義 Main。因為 IndexPage 和 DetailPage 都是直接在 body 上渲染的,所以這個 Main 也不需要干多余的事情,直接渲染 RouteHandler 就好
var Main = (function(R) {React.createClass({render: function() {return <R.RouteHandler params={this.props.params} />}}); })(ReactRouter);還是按處理 AMUIReact 的辦法來處理 ReactRouter,把它簡寫成 R。
然后是配置路由
var routes = (<R.Route path="/" handler={Main}><R.DefaultRoute handler={IndexPage} /><R.Route path=":id" handler={DetailPage} /></R.Route>);這里使用 Main 作為根路由處理器,默認路由也就是 #/ 的時候。渲染 IndexPage,所以把 IndexPage 作為默認路由(DefaultRoute)處理器。下一層路由是詳情頁面,只需要給個路徑參數 :id,用 DetailPage 作處理器即可。
最后啟動路由處理器
R.run(routes, function(Handler, state) {React.render(<Handler params={state.params} />, document.body);});處理器的回調函數中,第 1 個參數 Handler,就是在配置路由的時候給的根 handler 屬性,即對 Main 封裝而成的處理函數。而 state 表示了當前路由的狀態,包括路徑,參數等。其中 state.params 就是路由參數。通過 props.params 傳遞給 Main,再由 Main 通過 props.params 傳遞給 RouteHandler……
至于 React Router 是怎么處理各個路由的,這里不深入研究。有興趣的同學可以去研究 React Router 的源碼。
修改 detail.jsx
經過上面對 app.jsx 的修改,跑起來已經沒有問題了。問題在于詳情頁面顯示的總是“查無此人”。
之前的詳情頁面在加載數據的時候會根據 hash 來篩選數據,當時的 hash 像這樣:#1001。而現在 React Router 會將 hash 規范化處理成 #/1001。因此只需要將原來的
"#" + p.id === window.location.hash;改成
"#/" + p.id === window.location.hash;就好。
之前自定義的 router 就定義了路由參數,并且可以通過處理參數的形參獲取,再通過 props 傳遞給組件。但是因為偷懶,直接在組件內部通過處理 hash 來獲取了。簡單的路徑這么處理沒有問題,但是復雜的路徑處理起來就比較復雜了,所以還是應該用現成的。所以現在改用路由參數來篩選數據。
前面提到 React Router 一般是用 props.params 來傳遞參數,所以在 DetailPage 中可以通過 this.props.params.id 來獲取 ID 參數。
componentDidMount: function() {var id = this.props.params.id; // <--$.getJSON("/js/data.json").then(function(data) {if (this.isMounted()) {this.setState({person: data.filter(function(p) {return p.id === id; // <--})[0]});}}.bind(this)); }總結
以上是生活随笔為你收集整理的[学习笔记] Cordova+AmazeUI+React 做个通讯录 - 单页应用 (With Router)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小菜鸡进阶之路.文件操作遇到坑
- 下一篇: JavaEE互联网轻量级框架整合开发(书