慢牛系列三:React Native实践
上次发布了我的慢牛股票APP之后,有园友反馈有点卡,这个APP是基于Sencha Touch + Cordova开发的,Sencha本身是一个比较重的框架,在Chrome里运行性能还是不错的,但是在Android的WebView里,性能受限于机器的配置,在我的小米2s里表现还行,在小米4s里开起来比较流畅,但是Android机型相比IOS太多样了,Sencha Touch在iOS里表现不错,不过我还没编译iOS版本。
后来我又试着用了下Ionic框架,基于AngularJs开发,这个框架要轻量,在慢牛股票的微信公众号里试着开发了几个页面,感觉很不错,本来想用AngularJs重新做一个APP,但是后来又接触了React Native,试着用了下,React Native的编程体验类似于Sencha Touch,完全的组件化,JSX的语法很有意思,逐步习惯以后,用起来也很顺手。
有兴趣的朋友可以对比下这三个方案,我留下这个三个方案的下载地址:
慢牛股票APP(Sencha Touch+ Cordova)方案
慢牛微信公众号(Ionic方案)
Demo(React Native方案)
截图动画:
React Native相比上面两种,体验更顺畅,当然,原生的应用肯定是比在WebView里体验好的,ReactNative结合了原生开发和Web开发优势,不过,React Native现在对Android的支持不算太好,比如动画。
ReactNative的Flex布局,组件化思想很不错,上手快,不过因为最终显示的是原生,所以还是要多了解下原生的开发,必要时自己做桥接,比如我在做K线图,苦于找不到相关的组件,只有自己桥接了MPAndroidChart。
Demo主要实现了几个我认为比较关键功能:
1、导航
2、列表
3、下拉刷新
4、图表
这几个功能点实现以后,后面的工作就轻松得多了,比如表单提交,第三方登录,设置等等页面。
下面说下这几个点的实现方式,
1、导航:
侧滑菜单:
熟悉原生开发的同学一看就知道,这是原生的DrawerLayout,通过RN桥接过来,
很滑,很顺,之前用CSS3在WebView里做过侧滑菜单,当然比不上这个啦!
页面切换:
利用了RN的Navigator组件,这个组件实现了一个Stack,每次新开页面就是向Stack里Push新的UI导航信息,然后在Render时判断要显示哪个UI组件,这个导航组件会把历史UI移动可视范围之外,利用Opacity把UI设置为透明,然后绝对定位到可视范围之外,返回时,再把历史UI移动可视范围,这样,历史UI的状态还是保持的,这样的导航在Web上也是可以借鉴的。
这个导航还可以设置转场动画,看源代码,确实是实现了不少的动画,包括PushFromRight,FloatFromRight,FadeAndroid等,但是设置后不起作用,只有FadeAndroid有效果,但是动画一闪而过,不顺滑,这里对Android的支持不太好,或者是我的哪里设置不对?
Tab切换:
Tab切换做起来比较容易,不过有一点,在页面切换过程中,不要重新Render每个Tab对应的内容,重新Render会清除页面的当前的状态,比如滚动条的位置等,而且性能差,借鉴Navigator,把非焦点的Tab页面移动可视范围之外即可。
Demo中按钮的图标都是矢量的,图标字体的优点不用说了,原生系统也可以用图标字体了,对不喜欢用图片的同学真是一个喜讯,一想到要做那么多图片,要考虑每种分辨率,就头脑哇,这里用了github上的一个开源组件react-native-icons。
贴段代码:
render: function() { var navigationView = ( <SideMenu onItemSelected={this.onItemSelected}/> ); return ( <DrawerLayout ref="drawerMenu" drawerWidth={300} drawerPosition={DrawerLayout.positions.Left} renderNavigationView={() => navigationView}> <Navigator ref='navigator' debugOverlay={false} style={styles.appContainer} configureScene={(route) =>Navigator.SceneConfigs.FloatFromRight} initialRoute={{name:'main',component:(<TabPanel/>)}} renderScene={(route, navigator) => { return (route.component); }} /> </DrawerLayout> );
2、列表:
Demo里我实现了一个股票的列表,列表拆分成了List组件和ListItem组件,利用React开发,组件化思想很重要,这在之前用Sencha时也有这样的体验。
列表这里没有用ListView组件,是用ScrollVIew包了一个ListTtem的列表,如果数据量比较大,或者比较复杂列表,可以用官方的ListView来做,性能会比较好,每次更新只会更新变化的列表项,也提供了分类,设置表头和表尾等等。
贴段代码:
createRows:function(){ return this.state.myStockList.map(function (obj) { return (<StockItem key={obj.code} id={obj.code} data={obj}></StockItem>); }); }, render: function() { return ( <SwipeRefreshLayout ref={(control)=>{this._view=control}} style={styles.scrollView} onRefresh={this.reloadData}> <ScrollView style={{flex:1}}> {this.createRows()} </ScrollView> </SwipeRefreshLayout> ); }
3、下拉刷新:
在github上,有不少的下拉刷新组件,比如;react-native-gifted-listview,但是都是iOS上可以下拉,在Android上有Bug,文档上这样解释:
Pull-to-refresh in Android (tried to implement it but it seems that onResponderRelease event is not catchable yet in Android ListView - React-Native
关于如何在RN里响应手势,我还没怎么了解,后面再动画方面多研究下,因为了解下原生控件的桥接,所有就偷懒了,把原生的SwipeRefreshLayout控件做了桥接,包裹上ScrollVIew,就可以实现下拉刷新了,在JS里响应SwipeRefreshLayout发送的刷新事件,同时开放一个关闭刷新的接口,JS端获取数据更新后,关闭刷新状态。
4、图表
图表这里,我花的时间最多,目前也不是很完善,只不过,可以显示K线了,但是在交互上还是要加强。
之前在Web端做过两次K线图,一个是Sencha的,一个是D3的,用了RN以后,之前的图表都用不上了,也考虑过套个WebView,用D3来做,但是效果肯定不好哇,最后还是学习了原生开发,学习了原生UI的布局,组件的继承架构,学习使用MPAndroidChart组件,如何桥接原生组件等等,收获不小,现在开发一边打开Android Studio,一边打开Sublime。。。
原生的组件,View是所有UI组件的基类,而 ViewGroup是容纳这些组件的容器,其本身也是从View派生出来的,RN提供了两种ViewManager类用来桥接这两类组件,ViewManager管理组件的创建,布局,以及属性设置,事件触发。
贴段代码:
android端(桥接图表代码片段):
public class MPBarLineChartManager extends SimpleViewManager<BarLineChartBase> { private String CLASS_NAME="MPBarChart"; @Override public String getName() { return this.CLASS_NAME; } @Override protected BarLineChartBase createViewInstance(ThemedReactContext reactContext) { BarChart chart=new BarChart(reactContext); return chart; } @ReactProp(name="touchEnabled",defaultBoolean = true) public void setTouchEnabled(BarLineChartBase chart,boolean enable){ chart.setTouchEnabled(enable); }
...... }
js端:
var { requireNativeComponent,PropTypes } = require('react-native'); var iface = { name: 'BarChart', propTypes: { data:PropTypes.object, touchEnabled:PropTypes.bool, ...... scaleX: PropTypes.number, scaleY: PropTypes.number, translateX: PropTypes.number, translateY: PropTypes.number, rotation: PropTypes.number, }, }; module.exports = requireNativeComponent('MPBarChart', iface);
这个图表的桥接项目我发布到了github上,目前实现了对柱状图,线形图,以及组合图表的桥接。
https://github.com/hongyin163/react-native-chart-android
有希望用图表的同学可以利用下,要是能继续帮忙增强就更好啦!
学会了桥接原生组件以后,我就陷入了一种模式,只要在RN里实现有难度的,我就想桥接一个原生的Android组件,虽然知道这样不太好,因为如果用了太多原生的组件,后续的迁移麻烦了,当然最好利用官方提供的Android和IOS都支持的组件,最好是用平台无关的方式来做,这里最大的感触是,RN提供了一种途径,让js可以和原生组件交互,业务逻辑写在js里,利用原生组件呈现,如果有原生开发的同学支持,那么利用RN开发就方便多了,提供一致的组件库,一套代码就可以在两个系统上运行了,这样就不仅仅是learn once write anywhere了。
关于性能优化:
1、延迟加载
如果一次加载太多的组件,RN需要等全部组件渲染完成才显示出来,会有白屏,所有每次加载组件最好少一些,比如先构建页面的框架,框架渲染完成后再加载数据,创建基于数据的组件,或者利用setTimeout延迟加载其他的组件。
2、使用setNativeProps
React把组件当成状态机,通常是用setState方法,改变组件状态,页面会重新渲染,但是重新渲染性能比较差,setNativeProps可以绕过这个过程,直接修改原生组件的属性,或者调用原生的组件的API,性能就好多了,但是状态同步方面就要多考虑下了。
3、实现shouldComponentUpdate方法
React在组件渲染时会有个diff算法,如果前后Virtual DOM状态改变,会重新渲染组件,如果实现shouldComponentUpdate方法,返回false,就会避免不必要的diff计算和渲染。
关于React,这篇文章把他的前世今生都说了,写得很好:通往全栈工程师的捷径 —— react
React真是无所不在了,React-DOM,React-Canvas,React-Native,看起来前途无量哇!
最后,大家可以关注慢牛股票的微信公众号:
发送react,可以获取demo的apk文件,安装体验。
这个项目的源码后续会提交到github上,文章写的一般,见谅!欢迎大家评论留名^_^