上个迭代用React-native做了一个一期新的需求,在开的过程中举步维艰,如履薄冰啊,做过react的同学也知道,现在React-native 版本才到0.43,还不算一个成熟的技术,开发的过程中就是不确定性,开发一个新需求用着一个心里没底的技术,还是真是有点小压力,不过还好,问题归问题,但都不是什么大问题,想必一个只有0.43 的版本到现在 其实已经不错了,我相信React-native 以后会有很大的潜力的,废话不多说,说一下自己的心得,供大家参考,如果有什么不对的,还请轻喷,然后咱们再共同商讨,一起学习。
一、调试
1.我开发Rn喜欢打log,项目里每个方法,每个生命周期 数据回调之类的通过log去看,执行的顺序一幕了然,我不用Debug 模式调试是一因为debug 速度慢,然后还得一层一层打开去找源码打断点,二是因为 Debug是个终极武器似的,现在的程度还用不到打断点的方式,除非出什么诡异的问题你看不到log ,这个时候Debug也是个很好的工具。关于打log 请看我的这篇文章React-native调试小技巧,在Logcat输出console的log,不用摇晃也能弹出Debug 弹窗
2.用数据线连接着真机去调试,而不是用无线。有人问 既然有无线了为什么还用数据线,这您有所不知,在我们公司,不知怎么得,公司的公共WiFi(集团级的无线网络)不知道封了什么东西,导致在同一个局域网下边的机器找不到另一个机器端口8081下的bundle包,Charles也不管用了,坑爹的WiFi啊,我们采取了用网线分一个自己的无线热点,然后这样就可以了,不过这两个各有优点,无线调试就是方便,不用限定在自己的工位,自由拿着手机随便跑,但是每次都得去debug弹窗输入ip:port,累觉不爱,但是基于自己开发的情况,在工位开发不用随便乱跑,然后数据线开发在终端输入一条命令就搞定
adb reverse tcp:8081 tcp:8081
不过这个也有缺点,就是拔掉了数据线 然后再重新按上的时候还得重新adb reverse tcp:8081 tcp:8081
,插入别的iPhone手机也会导致 必须重新reverse,总之要不是因为网络的原因 我也不会选择用数据线调试。
二、继承。
个人认为js也是面向对象型的编程语言,只不过不用提前编译运行。所以就大胆的想出了继承的想法。在js中 继承的目的主要是为了复用UI,并且逻辑性不大的UI,纯粹只是为了展示,并且UI差异不大的话,可以用继承的方式去减少代码的编写(ps:减少的那些写代码的时间还不够出问题调试的时间呢,唉),比如 我有个详情页的展示,只不过有个按钮的展示的ui和逻辑是不同的,大部分是可以复用的,所以父类的写法应该是
export default class BaseDetailPage extends Component {
// 构造
constructor(props) {
super(props);
// 初始状态
this.state = {};
}
componentDidMount() {
this.getData();//去获取详情页数据,每个详情页的接口是不一样的,所以这个方法应该是在子类里面
}
componentWillUnmount() {
}
/**
* 数据成功的回调
* @param response
*/
handleAppDetailSuccess(response) {
this.setState({
appDetail: response.data,
});
}
/**
* 数据获取失败的回调
* @param response
*/
handleAppDetailFailed(response) {
//做些失败的处理
}
render() {
return (
<View style={styles.container}>
{this.renderLogo()}
{this.renderSizeInfo()}
{this.renderButton()}//这一块是调用的子类的方法
</View>)
}
/**
* 自己去写Ui
*/
renderLogo() {
}
/**
* 自己去写Ui
*/
renderSizeInfo() {
}
}
子类A的代码:
export default class DetailA extends BaseDetailPage {
// 构造
constructor(props) {
super(props);
// 初始状态
this.state = {};
}
/**
* 如果子类也要用这个生命周期,必须先调用父类的
* 不然就不会执行了
*/
componentDidMount() {
super.componentDidMount();
}
/**
* 子类获取详情页信息,但是最终的数据还是要回传给父类,所以调用父类的回调方法
* 比如
*/
getData() {
NetUtil.get("http://www.baidu.com", this.handleAppDetailSuccess.bind(this),
this.handleAppDetailFailed.bind(this))
}
/**
* 每个子类必须重写这个方法,因为父类会调用
* 重写这个空方法表明这个子类什么都不做
* @returns {XML}
*/
renderButton() {
return <TouchableHighlight onPress={()=> {
}
}>
<Text> 我是子类A 的button</Text>
</TouchableHighlight>
}
}
子类B的实现:
export default class DetailB extends BaseDetailPage {
// 构造
constructor(props) {
super(props);
// 初始状态
this.state = {};
}
/**
* 如果子类也要用这个生命周期,必须先调用父类的
* 不然就不会执行了
*/
componentDidMount() {
super.componentDidMount();
}
/**
* 子类获取详情页信息,但是最终的数据还是要回传给父类,所以调用父类的回调方法
* 比如
*/
getData() {
NetUtil.get("http://www.baidu.com", this.handleAppDetailSuccess.bind(this),
this.handleAppDetailFailed.bind(this))
}
/**
* 每个子类必须重写这个方法,因为父类会调用
* 重写这个空方法表明这个子类什么都不做
* @returns {XML}
*/
renderButton() {
return <TouchableHighlight onPress={()=> {
}
}>
<View>
<Text> 我是子类B 的button</Text>
<Text> 我比A多了好几个view</Text>
<Text> 我们都复用这个方法</Text>
</View>
</TouchableHighlight>
}
}
总结:
1.注释大体说了一下子类父类之间的关系,其实这样写还是有很对优点的,比如在一个很复杂的页面中,复用父类是很简单的一个事情,每块代码逻辑简单明了,没有业务的耦合,不用标志位字段判断 。
2.复用父类会出现一些问题,比如那个生命周期的复用,必须调用父类。其实和android中的生命周期有点类似。特别说明一些state
,子类和父类的state
都是相互可见的,但是必须先定义才能调用,比如父类定义this.setState({appDetail:null})
,子类如果想用这个父类的字段,必须先定义这个字段。赋值的事情就交给父类就行了。
3.本人亲测,在用父类的情况下,Rn的hot-loading 貌似失效了,修改了子类只会弹出toast,然后页面没有变化,必须重新退出之后然后在进入,才能看到。如果修改了父类会报错
undefined is not an object (evaluating ‘internallnstance._pendingForceUpdate’)
解决办法 重新reload就行了,这个我不能解释。
三、DeviceEventEmitter的正确用法
说DeviceEventEmitter 这个类之前先介绍一些这个类是干嘛的。DeviceEventEmitter类似于android的广播,只要注册了要监听的东西,并且有相应的发送消息的地方,就会收到这个广播。
主要用在两个地方:
- native 像js发送信息
- js 向js发送消息
一般我们会在js初始化的函数componenDidMount()
去注册监听
//第一步
componentDidMount() {
this.addProgressListener();
}
//第二步
addProgressListener() {
this.progerss = DeviceEventEmitter.addListener('onProgress', this.progressListener.bind(this));
}
//第三步
progressListener(params) {
}
当然你也可以一气呵成的去完成这个注册监听的方法,看方法名也知道这个是个下载的过程,我把下载的任务放到了 native层面去做,需要rn实时的去展示,
看第三部的param,这个数据结构是jsonObject还是jsonOBjectArray 还是要看native怎么拼装的。
public void sendProgress(String s, int progress) {
// 发送参数
WritableMap map = Arguments.createMap();
map.putInt(PARAM_PROGRESS, progress);
map.putString(APP_ID, s);
//{"appId":124,"progerss":30%} 这是一个jsonObject 当然你可以根据业务拼接 //Arguments.createArray()
getReactApplicationContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("onProgress", map);//这个消息发送只会发送到监听`onProgress `这个字段的地方
}
个人感觉Rn的这个消息机制还是不错的,但是如果你感到满足了而不继续学习,接下来会让你很痛苦的。
为什么呢?因为你学会了注册,但是没学会移除监听,会引发很多的问题。
比如这个
setState(…): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op
其实 这个不算问题,只能算一个warning,但是有warning 了那么离error还远吗。其实这个warning是因为你页面都卸载了,但是这个监听还存在着,就和android的内存泄露一样,一直刷着没有mount的组件。那么只需要正确的移除就行了,楼主当你就是没有用到正确的移除方法,导致遇到了一个问题查了两天,竟然没查到,最后才想到是移除监听的方法写错了。
正确的移除方式
this.progress.remove();//这个值请看第二步
也可以这样
DeviceEventEmitter.removeAllListeners();// 如果这个有back的监听,
//也会把 back健的监听也除去,不建议用这个方法
四、小知识
1.项目中用了一个第三方库react-native-progress这个库的star有700多
但是这个项目有个问题 ,就是在debug的模式下 会使用indeterminate
属性为true
会崩溃,所以 就考虑到了有没有一个全局的变量能判断是在release模式还是在debug模式。其实是有的 这个值就是__DEV__
if(__DEV__){
//Debug 模式
}else{
//Release模式
}
2.如果在一个方法中调用this.setState()
两次,那么整个页面也会render两次。
五、工具
作为开发,各种工具必须的玩的666的,如果我作为面试官,对于工具的使用必须会在我的面试范围之内,一个不爱瞎折腾,一个不愿接受新事物的人谈什么创造力。
1.Charles 一个很牛逼的抓包神器。这次主要用了他的抓Https、模拟慢网速、
模拟网速
1、入口:Proxy--Throttle Settings
2、可在“Throttle Preset”下选择 预置的网络配置(28.8~256kpbs、3G等)
3、可调节带宽、利用%比、延迟,来模拟网络环境。
备注:用的多的就是可用Throttle Preset设置2G 3G 4G,模拟不同网速的移动网络下app运行情况; 设置延迟,查看高延迟情况下,app提示及处理是否正常。
Https抓包
- charles的设置
1.1 选择 help | Install Charles CA SSL Certificate
1.2 然后会密码输入框,然后输入点击确定,Charles会把证书写进你的钥匙串了
1.3 选择Proxy | Proxy Settings,弹出proxy设置选项卡,勾选Enabling transparent HTTP proxying
1.4 选择ssl,勾选Enable SSL Proxying,在Location部份选择add,按如下图添加,抓取任意站点、443端口的数据
1.手机安装证书。
下载证书 http://www.charlesproxy.com/documentation/additional/legacy-ssl-proxying/
2.安装证书
2.1、Android:把证书放到储存设备上,然后 设置-安全-从SD卡安装
怎么把证书放到存储设备上,这个是我要说的,看着测试没还要打开文件存储,然后复制粘贴真是费劲
发送文件
adb push charles-proxy-ssl-proxying-certificate.crt /sdcard/
[100%] /sdcard/charles-proxy-ssl-proxying-certificate.crt
支持重命名
拷贝文件
adb pull /sdcard/charles-proxy-ssl-proxying-certificate.crt
[100%] /sdcard/charles-proxy-ssl-proxying-certificate.crt
2.2、ios:先用邮件把证书发到IPHONE上的邮箱,用iFile打开安装
其实Charles还有很多强大的功能,比如Map、Rewrite等功能,其实还都用过,但是在这就不多说了,要说的话就要另开新帖了,这儿重点是讲rn
最后提醒大家一定要仔细,往往稍微一不留神就会造成一个很小的bug但是你不得不花费时间去调试bug,到时候回过头来你会恨自己为什么当初不仔细一点,这也证明了很多bug就是因为开发的时候不仔细造成的。
今天是五一假期啊,我却在这码字~祝大家有个好的假期。