前端之H5與App交互總結

交互方式

前端通過將自身的方法掛載到window對象上,App端可以找到並異步回調,通過方法參數的形式將數據傳到前端,掛載到window上的方法名字需要兩端協議約定。

JS:
	let token = '';
	function foo(token_: string) {
   
   
		token = token_;
	}
	//當App端調用getToken的時候觸發綁定的foo方法
	window.getToken = foo;
App:
	// 僞代碼
	window.getToken('123abc');

封裝—讓頁面更簡潔、易維護

封裝的目的:1、App有兩種系統(IOS、Android),如果直接寫在頁面上,會使頁面有很多冗餘的判斷。

	if ( /(Android)/i.test(navigator.userAgent)) {
   
   
		//Android
		//...
	} else if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
   
   
		//IOS
		//...
	}

所以我們單獨將IOS和Android封裝成兩個獨立的類,再通過工廠模式判斷當前的系統類型,自動生產對應的類(以下代碼統一用TS做演示)

TS:
	class AppFactory {
   
   
		static getUserInstance() {
   
   
			//這裏考慮到全局使用的情況
			//所以採用單例的模式
			if (this.isIos()) {
   
   
				return IosUser.getInstance();
			} else if(this.isAndroid()) {
   
   
				return AndroidUser.getInstance();
			} else {
   
   
				return PcUser.getInstance();
			}
		}
		
		private static isIos() {
   
   
			return /(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)
		}

		private static isAndroid() {
   
   
			return /(Android)/i.test(navigator.userAgent)
		}
	}
	
	//現在,我們在頁面上再也不用寫if判斷了
	//只需要這樣寫就可以拿到當前的系統對應的類了
	AppFactory.getUserInstance()

下面我們繼續完善IOS類和Android類,我們需要遵循面向抽象類編程的思維,而不是具體的某一類,所以它們還需要一個公共的父類User。

TS:
	//抽象父類
	abstract class User {
   
   
		protected some:string|null = null;
		
		//通過init將協議約定的方法掛載到window上
		//這裏this指針會發生隱式綁定到window,需要使用bind強制綁定到自身
		init() {
   
   
			window.setSome = this.setSome.bind(this);
		}
	
		private setSome(some_: string) {
   
   
			this.some = some_;
		}
		
		public getSome_() {
   
   
			return this.some;
		}

		abstract applySome():void;	//這是由H5主動調用App端的方法,由各自子類去實現
	}

	//Android類
	class AndroidUser extends User{
   
   
		//上面提到兩個類會使用單例模式,以下不在贅述
		private static androidUser: AndroidUser|null;
	    private constructor() {
   
   
	        super();
	    }

	    static getInstance() {
   
   
	        if (!this.androidUser) {
   
   
	            this.androidUser = new AndroidUser();
	            return this.androidUser;
	        }
	
	        return this.androidUser;
	    }

		public applySome() {
   
   
			//someFunc兩端協議約定的方法名
			 window.someFunc.applySome()
		}
    }
	
	//Ios類
	class IosUser extends User {
   
   
		private static iosUser: IosUser|null;
	    private constructor() {
   
   
	        super();
	    }

	    static getInstance() {
   
   
	        if (!this.iosUser) {
   
   
	            this.iosUser = new IosUser();
	            return this.iosUser;
	        }
	
	        return this.iosUser;
	    }

		public applySome() {
   
   
			//對比AndroidUser類的applySome方法
			//可以看出調用App端方法時,兩個系統的處理方式有差別
			window.webkit.messageHandlers.applySome.postMessage(null)
		}
	}

	//底層封裝完畢後,在頁面調用就會非常清晰、優雅
	//例如我們需要拿some這個字段
	AppFctory.getUserInstance.init();	//這一段代碼在全局只需要初始化一次
	let some = AppFctory.getUserInstance.getSome();

如何完美抓住異步調用的時機—發佈訂閱模式

上面的代碼,已經可以支持我們優雅的在頁面與App端進行交互,但是還存在一個嚴重的Bug,App端是異步調用我們的方法,按照上面的寫法,App端還沒調用我們的方法就進行賦值操作,這樣是肯定拿不到值的。
如何解決呢?這裏我使用的是發佈訂閱模式,當監聽到App端調用我們的方法後,通知頁面

TS:
	//首先我們需要一個調度中心的類 NotificationCenter
	class NotificationCenter {
   
   
		public eventId: symbol;
		constuctor() {
   
   
			this.eventId = Symbol('eventId');
		}
		
		public register<T extends Event>(observe: object|symbol, event: Class<T>, cb: ()=>void) {
   
   
			let map = (<any>event).prototype[this.eventId]
					= (<any>event).prototype[this.eventId] || new Map<object|symbol, ()=>void>();

			map.set(observe, cb);
		}

		public notify(event: Event) {
   
   
			let map: Map<object|symbol, () => void> 
				= (<any>event)[this.eventId] || new Map();

			for (let [key, value] of map) {
   
   
				value();
			}
		}

		public unRegister(evemt: Event, observe: object|symbol) {
   
   
			let map =  Map<object|symbol, () => void> 
				= (<any>event)[this.eventId] || new Map();

			if(!map.has(observe)) {
   
   
				return;
			}

			map.delect(observe);
		}
	}

	interface Class<T> {
   
   
		prototype: T;
	}

這裏實現方式大家可以不用太在意細節,主要的思想還是運用發佈訂閱模式的註冊和監聽,將這個類掛到我們的User類上,在頁面註冊監聽,當方法被App端調用後,在最後發起通知,這樣就可以監聽到方法被成功調用了。

TS:
	class User {
   
   
		public nc_: NotificationCenter = new NotificationCenter();
	
		public setSome() {
   
   
			//...
			this.nc_.notify()	//通知頁面方法已經調用完畢
		}
	}

	//頁面
	AppFactory.getUserInstance.nc_.register(..., () => {
   
   
		//監聽到變化後執行
		let some = AppFactory.getUserInstance.getSome_();
	})

拓展和總結

以上基本的架構就成型了,可能交互的時候會通過JSON的形式,那麼就需要定義一個JSON類來處理格式等等,可以很好的拓展和集成。其實兩端交互本質上很簡單,但如果交互非常頻繁就會導致頁面冗餘判斷過多,難於維護,以上是我自己的一點經驗和總結,也算是拋磚引玉,如有不足的地方,希望大家多多指正、評價。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章