好久沒寫什麼東西了,最近在做一個ionic2的小東西,遇到了不少問題,也記錄一下,避免後來的同學走彎路。
之前寫過一篇使用VS2015開發ionic1的文章,但自己還沒摸清門道,本來也是感興趣就學習了一下。後來看到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;
}
}
零零散散寫了一些,不知道有沒有人遇到過類似的問題。目前用ionic2做正式專案的應該還不多,希望用過的同學多多交流。^_^