感覺TypeScript真不錯,強類型,有點類似c#的感覺,而且如果寫錯了編輯器都可以感知出來,於是就開始看ionic2。ionic2是基於angular2的,語法跟以前有了很大的變化。但自己寫原生app寫慣了,反而覺得這種方式更方便一些。每個頁面都是一個組件,組件里也可以套組件,html標簽都可以自定義,也就可以無限擴展。雖然ionic2和angular2都還沒發布正式版,但手頭的這個小東西用一下也未嘗不可,就開始動工了。
先列一下學習資源:
TypeScript中文手冊,這個網站應該是官方團隊的中國人搞的,非常好,我看到的時候已經把英文版看完了,記不清的時候會再來翻一下,地址:https://www.gitbook.com/book/zhongsp/typescript-handbook/details
angular2中文手冊,這個網站出來不久,對學習非常有幫助,找到的時候也是已經把英文版文檔看了一半多了,而且這個網站好的地方是可以同時把中文和英文對照着看。地址:https://angular.cn/docs/ts/latest/quickstart.html
ionic2文檔,一些指令基本跟1代類似,但用法有些變化,地址:http://ionicframework.com/docs/v2/
開發工具強烈推薦VS Code,現在已經非常好用了,對TypeScript的智能感知甚至比VS2015都要好。還需要安裝一些插件,我安裝了和angular2有關的插件,可以快速生成一些代碼段。下載地址:https://code.visualstudio.com 插件可以在商店里直接搜,很方便。
這篇文章不想再從hello world開始了,如果有耐心的話,照着官方文檔敲一遍都能正常運行起來。參考這個文檔:http://ionicframework.com/docs/v2/getting-started/installation/
前提是要安裝好nodejs。用npm安裝ionic和Cordova。就可以用ionic start projectname --v2 來開始項目了。這里要注意下,因為GFW的存在,有很大可能性會下載失敗,因為ionic2基於angular2,需要下很多依賴,我新建一個項目后,node_modules目錄大小是80多m,所以下載一定要有耐心,或者掛VPN。
新建項目后可以用ionic serve命令運行起來,可以在瀏覽器里看效果。
如果要添加Android平台支持,用ionic platform add android命令。
部署到真機的話,用ionic run android命令。或者ionic build android來編譯。
問題一:因gradle下載不到導致編譯失敗
編譯的時候會遇到gradle下載不下來的問題,導致編譯失敗。
解決辦法:手動下載gradle,http://downloads.gradle.org/distributions/gradle-2.2.1-all.zip
修改 appname\platforms\android\cordova\lib\builders 目錄下的GradleBuilder.js,找到類似下面的地方:
var distributionUrl = process.env['CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL'] || 'http\://services.gradle.org/distributions/gradle-2.2.1-all.zip';
修改為本地地址,我是放在了iis下面,改成了localhost。就能找到了。
問題二:打包錯誤,提示Unable to start the daemon process.
這個問題找了很多原因,有的說要改gradle.properties,也不管用,后來我刪掉了D:\yourusername\.gradle文件夾,重新編譯才過。如果失敗一次的話,重新編譯的話還是會失敗,只能刪掉重新來。
以上這兩個問題是打包到Cordova的時候遇到的,還有一些其他的問題就沒記下來,比較大的原因就是網絡沒下載到某些文件所致。我們是已經有了一個Cordova的平台,只做里面的html5插件即可,所以打包這部分沒再仔細研究。
問題三:Click Delays 點擊延遲問題
熟悉前端的應該都知道,某些元素在click事件會有300ms的延遲,在ionic里也是只有button和a可以立即響應的。如果要給其他的元素比如div增加click事件,給該元素加上tappable屬性即可解決。
問題四:http請求跨域問題
在ionic2里使用angular2的HTTP請求api時,如果在瀏覽器里運行,經常會遇到跨域問題,比如:
XMLHttpRequest cannot load http://www.xxx.com/clt/jsp/v3/channelContList.jsp?n=25950&WD-UUID=864819028898243&pageidx=1. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8101' is therefore not allowed access.
這是因為chrome不允許跨域訪問。解決方法很簡單,給chrome裝一個ripple擴展,然后點擊ripple,選擇啟用,就可以跨域訪問了。
如果是自己同時開發api和app,很有可能api也是部署在本機上,比如api地址是http://localhost/api,ionic serve跑起來后是http://localhost:8100,這樣在調用的時候又會遇到Internet Server Error的問題,比如:
Error code is:xhr_proxy?tinyhippos_apikey=ABC&tinyhippos_rurl=http%3A//localhost%3A30673/api/user/Get%3Fjson rippleapi.herokuapp.com Status Code:500 Internal Server Error I'am getting data from my localhost post adress:localhost:30673/api/user/Get'; It is working well in browser . And getting data from localhost:30673/api/user/Get. But in ripple it tries to get data from There: xhr_proxy?tinyhippos_apikey=ABC&tinyhippos_rurl=http%3A//localhost%3A30673/api/user/Get%3Fjson rippleapi.herokuapp.com
解決方法也很簡單,ripple設置右上角有一個Cross Domain Proxy,有三個選擇,Disabled、Local和Remote,通過字面意思就可以看出來分別對應禁用、本地和遠程訪問,如果是訪問本機的api的話,一般設置為Disabled就可以了。如果訪問遠程主機的api,一般要設置為Remote或Disabled。
問題五:引用第三方js庫的問題
開發過程中不可避免的要用到第三方js庫,如果直接在TypeScript里寫的話,編譯器是認不出來的,會報錯,編譯也通不過。外部的類必須要import進來才可以用。TypeScript需要一個聲明文件 d.ts來知道第三方庫的接口。可參考 https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Writing%20Definition%20Files.html
如果用流行的庫的話,不用我們自己寫d.ts,有個開源的項目已經做好了:https://github.com/yanxiaodi/DefinitelyTyped
自己寫的話很麻煩,特別是我用了一個項目平台的庫,函數也不少,自己寫的話也費時間,后來想到一個辦法,TypeScript的編譯器支持自動生成d.ts,可以用命令
tsc --declaration my.ts來生成,這個命令是給ts文件生成聲明的,但TypeScript原生支持js,可以把第三方的js改后綴名為ts,tsc也可以生成。這里我又遇到一個問題,我的庫里又調用了Cordova的一些函數,編譯的話tsc找不到,解決辦法是復制一份js,將所有認不出的東西都注釋掉,再生成就可以了。反正這個命令只是生成一個聲明文件,具體的js只要引入進來就可以用。用這個命令很快就可以生成一份聲明了,然后在用到的地方用
/// <reference path="../sdk.d.ts"/>
這樣的方式引用。注意一定要寫在文件第一行。
問題六:開發模式選擇
這個問題只是我做的項目的特殊情況,可能大部分人遇不到。我們的平台封裝了Cordova的http請求,調用api必須用指定的方法才可以。但在chrome里調試的時候是加載不到Cordova的,於是我想了一個辦法,增加一個全局的isDebug變量,封裝一個全局的http方法,在debug模式時調用angular2的HTTP來請求,正式運行時才用Cordova的。其他的service都要調用這個方法,就無需關注是什么模式了,如果真機運行的話就改一下isDebug的值就可以了。
放一段代碼:
1 /// <reference path="../sdk.d.ts"/> 2 import {Injectable, Component} from '@angular/core'; 3 import {HTTP_PROVIDERS, Http, Response} from '@angular/http'; 4 import {Headers, RequestOptions} from '@angular/http'; 5 import {AppGlobal} from '../app-global'; 6 7 8 /** 9 * HttpRequestService 10 */ 11 @Injectable() 12 @Component({ 13 providers: [HTTP_PROVIDERS,Http] 14 }) 15 export class HttpRequestService { 16 constructor(private http: Http) { 17 18 } 19 20 21 /** 22 * get方法 獲取json對象 23 * 24 * @template T 25 * @param {string} server 26 * @param {string} url 27 * @returns {Promise<T>} 28 */ 29 get4Json<T>(server: string, url: string): Promise<T> { 30 if (AppGlobal.getInstance().isDebug) { 31 return this.http.get(server + url).toPromise() 32 .then(response => response.json()); 33 } 34 else { 35 let promise: Promise<T> = new Promise<T>((resolve, reject) => { 36 //由於SDK必須要求傳入一個參數數組,因此必須傳遞一個空數組作為參數 37 let paramJson = []; 38 SDKRequest.get4Json(server, url, paramJson, function (resp) { 39 resolve(resp); 40 }, function (error) { 41 reject(error); 42 }); 43 }); 44 return promise; 45 } 46 } 47 48 }
angular2的http是用的Promise,但平台提供的方法用的callback,於是需要在這里將回調函數的方式改為Promise的方式,不管是不是debug模式都返回一個Promise,這樣上層調用的時候就方便了。我是看的這里:https://basarat.gitbooks.io/typescript/content/docs/promise.html
在angular2的官方文檔中,是推薦用Observable模式的,但我還沒有搞明白怎么將callback轉為Observable,目前也沒有時間仔細研究這塊,所以還是繼續用Promise好了。
問題七:單例模式
單例是經常用到的,我參考一個老外的代碼用了一個單例,用來保存一些全局變量:
import {UserInfo} from './model/user'; /** * AppGlobal 全局定義 單例模式 */ export class AppGlobal { private static instance: AppGlobal = new AppGlobal(); /**是否是調試狀態 */ isDebug: boolean = true; server: string = this.isDebug ? "http://localhost" : "http://www.xxx.com"; apiUrl: string = "/MobileApi/api"; /**當前用戶信息 */ currentUserInfo: UserInfo = new UserInfo(); /**分頁頁數 */ pageSize: number = 10; constructor() { if (AppGlobal.instance) { throw new Error("錯誤: 請使用AppGlobal.getInstance() 代替使用new."); } AppGlobal.instance = this; } /** * 獲取當前實例 * * @static * @returns {AppGlobal} */ public static getInstance(): AppGlobal { return AppGlobal.instance; } }