在weex中,從.we
過分到.vue
的過程當中,很難規避的就是事件派發機制當中,對BroadcastChannel
的替換,按照官方的推薦採用vuex去更換,可是我在瀏覽一遍vuex
的文檔以後,絕的在weex
使用有點麻煩,就去社區溜達了一圈,看看有沒有小夥伴們找到更合適的方法。html
在一陣交流以後,根據大夥的推薦,在.vue
文件中,都是採用 weex
提供的globalEvent來處理。vue
此次的踩坑記,也是這個文檔帶來。下面我就來記錄一下,此次踩坑的歷程。git
按照文檔的要求,在fireGlobalEvent
的時候,須要各端實現,所以按照要求在Objective-C
,添加如下方法:github
/** 發送全局事件 @param eventName 事件名稱 @param params 事件參數 */ - (void)postGlobalEvent:(NSString *)eventName params:(NSDictionary *)params { [weexInstance fireGlobalEvent:eventName params:params]; }
而且暴露給weex
使用: WX_EXPORT_METHOD(@selector(postGlobalEvent:params:))
vuex
準備好了這些,我就開始在.vue
的文件中開始測試功能了。apache
我是直接在個人項目中修改原先的代碼的,下面的demo
,也是我代碼的一部分,項目中廣場頁面中,navigator
組件上消息的觸發按鈕,換成調用剛剛native
中擴展的postGlobalEvent
方法, square-header.vue
代碼以下:json
<template> <div :style="{ width: '750', height: navHeight, backgroundColor: 'rgba(255, 255, 255, ' + (opacity) + ')' }"> <image src="https://static.toomao.com/weex-images/square/navigator3.png" class="nav-image" :style="{ opacity: opacity>0.8?0:(0.8-opacity) }"></image> <div class="nav-content" :style="{ marginTop: navHeight===128?40:0 }"> <div :class="['nav-left', 'nav-left-' + (navigatorState)]" @click="scannerButtonClicked"> <image :src="navigatorIcons[0]" :class="['nav-left-icon', 'nav-left-icon-' + (navigatorState)]" resize="contain"></image> <text :class="['nav-left-text', 'nav-left-text-' + (navigatorState)]">掃一掃</text> </div> <text :class="['nav-center', 'nav-center-' + (navigatorState)]" @click="searchTextClicked">{{tip.words ? tip.words : '請輸入搜索內容'}}</text> <div :class="['nav-right', 'nav-right-' + (navigatorState)]" @click="infoButtonClicked"> <image :src="navigatorIcons[1]" :class="['nav-right-icon', 'nav-right-icon-' + (navigatorState)]" resize="contain"></image> <text :class="['nav-right-text', 'nav-right-text-' + (navigatorState)]">消息</text> </div> </div> </div> </template> <script> const utils = weex.requireModule('utils'); module.exports = { methods: { searchTextClicked() { console.log('~~~~~~~~globalEvent 已經發送了~~~~~~~~~~~~~~~~~'); utils.postGlobalEvent('test1', { index: 'current index is 1'}); }, }, }; </script>
下面是square.vue
的監聽事件的代碼:api
<template> <div> <!-- navigator --> <square-header ref="square-header"></square-header> </div> </template> <script> ; const utils = weex.requireModule('utils'); module.exports = { components: { squareHeader: require('../components/navigator/square-header.vue'), }, created() { // 監聽事件 const globalEvent = weex.requireModule('globalEvent'); globalEvent.addEventListener("test1", (e) => { // 事件回調 console.log('~~~~~~~~test1~~~~~~~~~~~~~~~~~', e); }); }, };</script>
這樣我就基本完成了,這個demo的全部工做,而後build
,沒有報錯、最好run
,打開這個頁面,渲染成功,下面是我在點擊搜索按鈕,Xcode控制檯的打印信息:微信
2017-06-22 10:47:53.338723 [fg128,128,128; <Weex>[log]WXJSCoreBridge.m:110, jsLog: ~~~~~~~~globalEvent 已經發送了~~~~~~~~~~~~~~~~~ [; 2017-06-22 10:48:15.584984 [fg128,128,128; <Weex>[log]WXJSCoreBridge.m:110, jsLog: ~~~~~~~~test1~~~~~~~~~~~~~~~~~ {"index":"current index is 1"} [;
看到這結果,我表示心情還挺不錯,由於還挺好用的,感受一會兒找到了好的方式去替換這些方法了。weex
就在我開心的勁頭上,我繼續開始了我實際方法的更換,首先第一個就是在登陸成功後,要給多個頁面(我的信息、權限相關的頁面)發送通知,所以我開始了第二個測試,即在不一樣實例之間進行發送通知與監聽。本覺得信息十足的,結果出現的問題:
發送事件的實例:login.vue
:
<template> <scroller style="width: 750px; height: 1334px;"> <!-- bgImg、 back button --> <image :src="getterNativeImgSrc('navigator/login_bg')" style="width: 750px; height: 1334px; position: absolute; top: 0px; left: 0px;"></image> <image :src="getterNativeImgSrc('login/login_back')" style="width: 35px; height: 35px; position: absolute; top: 60px; left: 24px;" @click="backButtonClicked"></image> <!-- input --> <div class="userInfo"> <div class="inputWrapper"> <input type="text" name="username" class="input" ref="username" placeholder="請輸入您的手機號碼" maxlength="11" @input="oninput"> </div> <div class="inputWrapper"> <input class="input" :type="passwordType" name="password" ref="password" maxlength="20" placeholder="請輸入密碼" @input="oninput"> <image style="width: 30px; height: 30px; background-color: #0ff;" :src="openEyes ? eyeSelected : eye" resize="contain" @click="eyeButtonClicked"></image> </div> </div> <!-- 登陸按鈕 --> <wxc-form :action="(apiBase) + '/1.1/login?username=' + (userName) + '&password=' + (password)" method="GET" ref="login" style="margin-top: 40px;"> <text class="loginButton" @click="loginButtonClicked">登 錄</text> </wxc-form> <!-- 註冊 忘記密碼 --> <div style="width: 750px; flex-direction: row; justify-content: space-between; padding: 24; margin-top: 20px;"> <text style="font-size: 28px; color: #707070;" @click="signUpButtonClick">註冊帳號</text> <text style="font-size: 28px; color: #707070;" @click="forgotButtonClick">忘記密碼</text> </div> <div v-if="isInstallWX" style="margin-top: 350px; flex-direction: column; justify-content: center; width: 750px; align-items: center;" @click="thirdLoginButtonClicked"> <text style="font-size: 26px; color: #707070;">使用第三方登陸</text> <image :src="getterNativeImgSrc('login/weixin')" style="margin-top: 20px; width: 60px; height: 60px;" resize="contain"></image> </div> <wxc-form :action="(apiBase) + '/1.1/loginByWechat?unionid=' + (thirdUserInfo.unionid)" method="GET" ref="third-login" style="margin-top: 60px;"> </wxc-form> <tm-loading ref="tm-loading" inithide="true"></tm-loading> </scroller> </template> <script> ; const navigator = weex.requireModule('navigator'); const utils = weex.requireModule('utils'); const storage = weex.requireModule('storage'); const { serverPath, getNativeResourcePath, navigatorPushWithPath, toast, errorDeals } = require('../util.js'); module.exports = { components: { wxcForm: require('components/wxc-form/wxc-form.vue'), tmLoading: require('../components/tm-loading.vue') }, props: { apiBase: { default: serverPath() }, userName: { default: '' }, password: { default: '' }, userInfo: { default: function () { return {}; } }, openEyes: { default: false }, eye: { default: '' }, // 閉眼 eyeSelected: { default: '' }, // 睜眼 isInstallWX: { default: true }, thirdUserInfo: { default: function () { return {}; } }, // 微信用戶信息 loginButtonEnable: { default: true }, passwordType: { default: 'password' } }, created() { this.eye = getNativeResourcePath(this, 'login/login_eye'); this.eyeSelected = getNativeResourcePath(this, 'login/login_eye_selected'); if (weex.config.env.platform === 'iOS') { navigator.setNavBarHidden({ hidden: true }, () => {}); } try { utils.weexInstalledWeChatClient(e => { this.isInstallWX = e.result; }); } catch (e) {} }, mounted() { this.$refs['tm-loading'].hide(); }, methods: { backButtonClicked() { navigator.pop({ animation: 'ture' }, () => {}); }, oninput(e) { const id = e.target.attr.name; if (id === 'username') { this.userName = e.target.attr.value; } else { this.password = e.target.attr.value; } }, onchange(e) {}, loginButtonClicked() { if (this.userName.length != 11) { toast('請輸入正確的手機號碼', 1); return; } if (this.password.length < 6 || this.password.length > 20) { toast('請輸入6-20位密碼', 1); return; } const form = this.$refs.login; form.headers = { 'content-type': 'application/json' }; if (!this.loginButtonEnable) return; this.loginButtonEnable = false; const that = this; this.$refs['tm-loading'].show(); form.submit(res => { that.$refs['tm-loading'].hide(); that.loginButtonEnable = true; if (res.ok) { const data = JSON.stringify(res.data); storage.setItem('userInfo', data, event => { console.log('~~~~~~~~登陸成功 發送通知 ~~~~~~~~~~~~~~~~~'); utils.postGlobalEvent('login-success', 'login succeed'); // const Hulk = new BroadcastChannel('login-success'); // Hulk.postMessage('login succeed'); that.backButtonClicked(); }); } else { errorDeals(res); } }); }, // 第三方登陸 thirdLoginButtonClicked() { this.$refs['tm-loading'].show(); try { utils.getWeChatUserInfo(e => { this.$refs['tm-loading'].hide(); if (e.result === 'success') { this.thirdUserInfo = e.data; this.$renderThen(() => { this.requestThirdLoginUserInfo(); }); } else { toast('受權失敗', 1); } }); } catch (e) {} }, // 第三方登陸請求 requestThirdLoginUserInfo() { const form = this.$refs['third-login']; const that = this; form.submit(res => { if (res.ok) { // 存儲以前,先將對象序列化成存儲字符串 const data = JSON.stringify(res.data); storage.setItem('userInfo', data, event => { utils.postGlobalEvent('login-success', 'login succeed'); // const Hulk = new BroadcastChannel('login-success'); // Hulk.postMessage('login succeed'); that.backButtonClicked(); }); } else { const data = res.data; if (res.status === 400 && data.code === 4105) { // 第一次登陸 去綁定帳號 const userStr = JSON.stringify(this.thirdUserInfo); navigatorPushWithPath(`login/association-account.js?config=${encodeURIComponent(userStr)}`); } } }); }, // 註冊 signUpButtonClick() { navigatorPushWithPath('login/sign-up.js'); }, // 忘記密碼 forgotButtonClick() { navigatorPushWithPath('login/forgot-password.js'); }, eyeButtonClicked() { this.openEyes = !this.openEyes; this.passwordType = this.openEyes ? 'text' : 'password'; }, // 獲取圖片路徑 getterNativeImgSrc(src) { return getNativeResourcePath(this, src); } } };</script>
在上面代碼中,能夠定位到loginButtonClicked()
方法,這是登陸按鈕執行的方面,在這個方法請求成功後,我會調用 utils.postGlobalEvent('login-success', 'login succeed');
方法,即發送一個全局事件的通知,名字叫作login-success
;並在發送後返回到上一頁面。
監聽事件的實例: mine.vue
<template> <div style="background-color: #f4f4f4;" @viewappear="viewappear"> <wxc-form :action="(baseAPI) + '/1.1/my/pageinfo'" method="GET" ref="loaderPage"></wxc-form> <list style="width: 750px; height: 1244"> <cell> <mine-header ref="header"></mine-header> </cell> <cell> <mine-orders-toolbar ref="orders"></mine-orders-toolbar> </cell> <cell> <mine-more-tools></mine-more-tools> </cell> </list> <div class="navigator"> <div class="content"> <image src="https://pic.toomao.com/becb9c4ffda30defcda9b760b9478633bbdb7d22" style="width: 50px; height: 50px;" @click="settingButtonClicked"></image> </div> </div> </div> </template> <script> ; const { getBaseAPI, asyncReady, navigatorPushWithPath } = require('../util.js'); module.exports = { components: { wxcForm: require('components/wxc-form/wxc-form.vue'), mineHeader: require('../components/mine/mine-header.vue'), mineOrdersToolbar: require('../components/mine/mine-orders-toolbar.vue'), mineMoreTools: require('../components/mine/mine-more-tools.vue'), tmNavpage: require('../components/navigator/tm-navpage.vue') }, props: { baseAPI: { default: getBaseAPI() }, userInfo: { default: function () { return {}; } }, data: { default: function () { return {}; } } }, created() { const globalEvent = weex.requireModule('globalEvent'); console.log('~~~~~~~~addEventListener ~~~~~~~~~~~~~~~~~'); globalEvent.addEventListener("login-success", (e) => { console.log('~~~~~~~~addEventListener CallBack~~~~~~~~~~~~~~~~~', e); this.receiveLoginSuccessedNotify(); }); }, mounted: asyncReady(function () { if (this.userInfo.sessionToken) { this.requestPageInfo(); } }), methods: { viewappear: asyncReady(function () {}), receiveLoginSuccessedNotify() { asyncReady(function () { if (this.userInfo.sessionToken) { this.requestPageInfo(); } }).call(this); }, settingButtonClicked() { navigatorPushWithPath('mine/setting/setting.js'); }, requestPageInfo() { const header = this.$refs.header; const oreders = this.$refs.orders; const pageLoader = this.$refs.loaderPage; pageLoader.headers = { 'X-AVOSCloud-Session-Token': this.userInfo.sessionToken }; pageLoader.submit(res => { if (res.ok) { this.data = res.data; header.setUpCardData(res.data); oreders.setUpOrderNumber(res.data.ordercnt); } }); } } };</script> <style scoped> .wrapper { background-color: #eee; } .navigator { position: absolute; top: 0px; left: 0px; width: 750px; height: 128px; padding-top: 40px; /*background-color: #0f0;*/ } .content { width: 750px; height: 88px; flex-direction: row; justify-content: space-between; align-items: center; /*background-color: #999;*/ padding: 24; }
測試過程:先在未登陸的狀況下,訪問mine
頁面,而後點擊我的信息進入到登陸頁面,登陸成功後,發送通知,並返回到個人頁面,正常狀況下,個人頁面會接收通知,並從本地獲取新數據刷新UI的。但實際過程以下,能夠注意我代碼中的幾個log:
2017-06-22 11:11:25.084999 [fg128,128,128; <Weex>[log]WXJSCoreBridge.m:110, jsLog: ~~~~~~~~addEventListener ~~~~~~~~~~~~~~~~~ [; 2017-06-22 11:12:42.884790 [fg128,128,128; <Weex>[log]WXJSCoreBridge.m:110, jsLog: WXC-FORM?: [object Object] [; 2017-06-22 11:12:43.227708 [fg128,128,128; <Weex>[log]WXJSCoreBridge.m:110, jsLog: ~~~~~~~~登陸成功 發送通知 ~~~~~~~~~~~~~~~~~ [;
上面結果能夠看到,我監聽了事件,而且也發送了事件,可是我沒有收到事件的callBack。
爲了探究一下,這個事件爲啥沒有接收到,我跟着native
的代碼,進入到 weex SDK
去看看了具體實現。找到globalEvent
在iOS
的實現類WXGlobalEventModule
,(在尋找這個module
的時候,能夠直接根據globalEvent
在SDK
裏面搜索,這樣比較快) 並獲取addEventListener
方法:
- (void)addEventListener:(NSString *)event callback:(WXModuleKeepAliveCallback)callback { WXThreadSafeMutableArray * array = nil; if (_eventCallback[event]) { if (callback) { [_eventCallback[event] addObject:callback]; } } else { array = [[WXThreadSafeMutableArray alloc] init]; if (callback) { [array addObject:callback]; } _eventCallback[event] = array; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(fireGlobalEvent:) name:event object:nil]; } }
能夠看出在監聽方法中,主要是使用了NSNotification
添加了一個觀察者,而且將每一個evet
對應的callBack
都保存起來;所以找到接受通知的實現方法:fireGlobalEvent
的實現以下:
- (void)fireGlobalEvent:(NSNotification *)notification { NSDictionary * userInfo = notification.userInfo; NSString * userWeexInstanceId = userInfo[@"weexInstance"]; /* 1. The userWeexInstanceId param will be passed by globalEvent module notification. 2. The notification is posted by native user using NotificationCenter, native user don't need care about what the userWeexInstanceId is. What you do is to addEventListener in weex file using globalEvent module, and then post notification anywhere. */ WXSDKInstance * userWeexInstance = [WXSDKManager instanceForID:userWeexInstanceId]; // In case that userInstanceId exists but instance has been dealloced if (!userWeexInstanceId || userWeexInstance == weexInstance) { for (WXModuleKeepAliveCallback callback in _eventCallback[notification.name]) { callback(userInfo[@"param"], true); } } }
在處理通知的方法中,能夠發如今調用callback
以前有兩個判斷!userWeexInstanceId || userWeexInstance == weexInstance
, 要麼這個實例id不存在,要麼兩個實例相同,看到這裏彷佛能明白剛剛爲啥在login.vue
頁面中發送的事件在mine.vue
的監聽這沒有收到回調了。
那麼根據NSString * userWeexInstanceId = userInfo[@"weexInstance"];
代碼分析: 這個userWeexInstanceId
是通知的userInfo
裏面設置的。爲此我須要找到post這個通知在什麼位置。這時候確定就是native
暴露給weex
用來發送通知的那個方法了:
/** 發送全局事件 @param eventName 事件名稱 @param params 事件參數 */ - (void)postGlobalEvent:(NSString *)eventName params:(NSDictionary *)params { [weexInstance fireGlobalEvent:eventName params:params]; }
進入這個這個方法裏面獲得的代碼以下:
- (void)fireGlobalEvent:(NSString *)eventName params:(NSDictionary *)params { if (!params){ params = [NSDictionary dictionary]; } NSDictionary * userInfo = @{ @"weexInstance":self.instanceId, @"param":params }; [[NSNotificationCenter defaultCenter] postNotificationName:eventName object:self userInfo:userInfo]; }
哈哈哈,看到這裏就基本清楚全部的內容所在了,userInfo
這個參數也是在這裏設置的。其實走到這步我仍是不明白個人問題該怎麼解決,由於在通知callBack的兩個條件,該怎麼避免,我感受官方把本身的路給堵死了,所以帶這個問題去請求老司機, 獲得如下回應:
看到weex
見解這這樣的迴應,個人心裏微微一笑-_-。看來目前也只能這樣了,所以調整代碼:
/** 發送全局事件 @param eventName 事件名稱 @param params 事件參數 */ - (void)postGlobalEvent:(NSString *)eventName params:(NSDictionary *)params { if (!params){ params = [NSDictionary dictionary]; } NSDictionary * userInfo = @{ @"param":params }; [[NSNotificationCenter defaultCenter] postNotificationName:eventName object:self userInfo:userInfo]; }
再次運行獲得如下結果:
2017-06-22 11:40:02.134405 [fg128,128,128; <Weex>[log]WXJSCoreBridge.m:110, jsLog: ~~~~~~~~登陸成功 發送通知 ~~~~~~~~~~~~~~~~~ [; 2017-06-22 11:40:02.138522 [fg128,128,128; <Weex>[log]WXJSCoreBridge.m:110, jsLog: ~~~~~~~~addEventListener CallBack~~~~~~~~~~~~~~~~~ login succeed [; 2017-06-22 11:40:02.184861
哈;此次終於看到了~~~~~~~~addEventListener CallBack~~~~~~~~~~~~~~~~~
的打印信了,而且也將傳遞的參數login succeed
獲取了,至此,這個坑算是踩完了。