您的位置:首页 > 理论基础 > 计算机网络

React-Native傻瓜式学习笔记(三):基于事件发布/订阅的网络请求工具类封装

2017-01-18 16:42 666 查看
最近国内外使用React-Native写APP的公司越来越多了,我们公司也不甘落后,将使用React-Native重写APP这个事提上了日程。然而这个任务落到我头上的时候,我本人作为一个Android菜鸡程序猿当即表示“臣妾做不到啊~”,然而并没有什么卵用。没办法,只好硬着头皮现学现卖。这东西刚出时间并不算长,网上完整的系统的参考文章较少,多数都是零碎的知识点,只好在博客里做个笔记,省得忘了以后再去满大街找。。

这一系列笔记内容均仅代表我个人的观点,不保证都是对的

起因

在React-Native中进行网络请求时,我们通常会使用JS中提供的fetch方法,通过一系列Promise语法异步的获取服务器返回的信息。实际上,经过不断的演化,fetch方法已经非常易用且语法逻辑清晰。但在实际开发过程中,我们经常会出现写着写着满屏幕的各种fetch.then.then,简直烦的不行,加上每次请求根据业务需要还要传大量相同的header,几行真正的业务逻辑代码在一大坨重复代码中间瑟瑟发抖。。另外,由于RN基于组件(Component)开发的机制,有的时候会出现我需要在一个组件中进行网络请求,但我需要在另一个或多个组件中处理返回的数据(例如:在搜索框组件中发起搜索请求,在结果组件中展示返回的结果),这个时候使用fetch就非常尴尬。因此,网络请求工具的封装势在必行。作为一个深受OO思想荼毒的Android程序员,我第一时间想起了EventBus。

EventBus实现

Android中的EventBus是一个非常经典的基于观察者模式的事件发布/订阅框架,在Android开发中被广泛应用。为了解决我”一处发起请求到处接收响应“的需求,我首先就想到了它。因此立刻开始在RN工程中着手搭建,以下是我的EventBus简单实现:

//EventBus.js

let instance = null; // 单例引用
let eventSubscribers = {}; // 使用一个对象保存订阅者(可以视作一个JAVA中的Map,每个key对应一个事件,每个value对应所有订阅该事件的订阅者组成的数组)

export default class EventBus {
constructor() {
// 单例模式,实际上也可以不用,OO害我!
if (!instance) {
instance = this;
}
return instance;
}

/**
* 发送事件的方法,这个方法执行后会通知所有订阅了对应事件的订阅者
* @param event 事件内容
* @param type 事件名称,作为区分每个事件的标记
*/
sendEvent = (event, type) => {
let subscribers = eventSubscribers[type];
if (subscribers && subscribers.length > 0) {
subscribers.forEach((subscriber) => {
if (subscriber && subscriber !== null && typeof subscriber.handleEvent == 'function') {
// 这里调用了订阅者的handleEvent方法,每一个订阅者都要通过实现这个方法来处理接收到的事件
subscriber.handleEvent(event, type);
}
});
}
}

/**
* 订阅事件的方法
* @param subscriber 订阅者,即订阅事件的对象
* @param type 订阅的事件名称
*/
registerEvent = (subscriber, type) => {
let subscribers = eventSubscribers[type];
if (subscribers) {
subscribers.push(subscriber);
} else {
eventSubscribers[<
bde5
span class="hljs-keyword">type] = [subscriber];
}
}

/**
* 解除订阅事件的方法
* @param subscriber 订阅者,即订阅事件的对象
* @param type 要解除订阅的事件名称
*/
unregisterEvent = (subscriber, type) => {
let subscribers = eventSubscribers[type];
if (subscribers) {
// 从当前订阅了对应事件的订阅者数组中过滤掉解除订阅的订阅者
eventSubscribers[type] = subscribers.filter((sub) => sub !== subscriber)
}
}
}


这样一个简单的事件总线就完成了,需要注意的是,每一个订阅者如果想要接收并处理事件,都要自行实现handleEvent方法,在这个问题上,JS没有Interface这种东西让我浑身蓝瘦。然而作为一个菜鸡,我也不知道有什么更好的办法,暂时就先这样了。。放开这个问题先不管,接下来我们开始封装网络请求工具。

封装网络请求工具

基于上面写好的EventBus,网络请求的封装思路就很简单了:将复用的header封装,并将服务器返回的数据(或错误信息)作为一个event发布出去。工具本身不用知道是谁接收事件,订阅者也不用管我订阅的事件是谁发送的,气氛一片祥和。以下是简单的实现例子:

//ApiHelper.js

import {Platform} from 'react-native';
import EventBus from './EventBus';
import * as UserUtil from './UserUtil';
import * as AppInfo from '../constants/AppInfo';

/**
* GET请求
* @param url url地址,可以直接带参数,也可以通过后面的params对象传进来
* @param eventType 用来指定Event类型,通过注册对应的监听并实现handleEvent方法来处理返回的结果。<br>
* 正常情况返回json对象,出错返回错误对象,格式:{errorCode: number, msg: string}
* @param params 自定义参数对象,{key1: value1, key2: value2, ....},如果已经在url地址里写了这里就不用传
*/
export const get = (url, eventType, params) => {
if (params) {
let paramsArray = [];
//拼接参数
Object.keys(params).forEach(key => paramsArray.push(key + '=' + params[key]))
if (url.search(/\?/) === -1) {
url += '?' + paramsArray.join('&')
} else {
url += '&' + paramsArray.join('&')
}
}
if (url) {
//fetch请求
fetch(url, {
method: 'GET',
headers: { // 统一的headers
'deviceType': Platform.OS == 'android' ? 1 : 0,
'appVersion': AppInfo.APP_VERSION,
'token': UserUtil.getToken(),
'uid': UserUtil.getUid(),
},
}).then((response) => {
if (response.ok) {
response.json().then(responseJson => {
if (eventType) {
new EventBus().sendEvent(responseJson, eventType);
}
})
} else {
let error = {
errorCode: response.status,
msg: response.statusText
};
new EventBus().sendEvent(error, eventType);
}
}).catch((e) => {
let error = {
errorCode: -1,
msg: '',
};
new EventBus().sendEvent(error, eventType);
})
}
}

/**
* POST请求
* @param url url地址
* @param eventType 用来指定Event类型,通过注册对应的监听并实现handleEvent方法来处理返回的结果,出错返回错误对象,格式:{errorCode: number, msg: string}
* @param params 自定义参数对象,{key1: value1, key2: value2, ....}
*/
export const post = (url, eventType, params) => {
if (url) {
let paramsArray = [];
if (params) {
//拼接参数
Object.keys(params).forEach(key => paramsArray.push(key + '=' + params[key]));
bodyStr += paramsArray.join('&')
}
//fetch请求
fetch(url, {
method: 'POST',
headers: { // 统一的headers
'deviceType': Platform.OS == 'android' ? 1 : 0,
'appVersion': AppInfo.APP_VERSION,
'token': UserUtil.getToken(),
'uid': UserUtil.getUid(),
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
},
body: bodyStr,
}).then((response) => {
if (response.ok) {
response.json().then(responseJson => {
if (eventType) {
new EventBus().sendEvent(responseJson, eventType);
}
})
} else {
let error = {
errorCode: response.status,
msg: response.statusText
};
new EventBus().sendEvent(error, eventType);
}
}).catch((e) => {
let error = {
errorCode: -1,
msg: '',
};
new EventBus().sendEvent(error, eventType);
})
}
}


至此,网络请求工具就封装好了,让我们来看看怎么用吧(只列出关键代码):

//DemoComponent.js

//省略一大堆引用...

export default class DemoComponent extends Component {

...

componentDidMount() {
//组件渲染后订阅事件
new EventBus().registerEvent(this, 'demo1');
new EventBus().registerEvent(this, 'demo2');
this.getData();
}

componentWillUnmount() {
//组件销毁前解除订阅事件
new EventBus().unregisterEvent(this, 'demo1');
new EventBus().unregisterEvent(this, 'demo2');
}
//执行网络请求,并指定该请求对应的EventType
getData = () => {
ApiHelper.get('http://www.example1.com','demo1');
ApiHelper.get('http://www.example2.com','demo2');
}

//实现handleEvent方法以接收并处理事件
handleEvent = (event, type) => {
switch (type) {
case 'demo1':
//处理事件...
break;
case 'demo2':
//处理事件...
break;
}
};

...

}


总结

实际上EventBus不止能用来作为网络请求数据传递的工具,同时也可以作为一个非常方便的Component间传递数据的工具,不过再此就不赘述了。总之与网络请求工具配合起来相当的好用,随时随地发请求,想在哪接就在哪接,简直一身轻松。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: