在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
获取了,至此,这个坑算是踩完了。