ReactNative能做出那种特别qt自定义精美换肤界面的界面么

今天看啥 热点:
React Native 调研报告,reactnative
Facebook三月份开源了React Native iOS平台的框架,让移动开发人员和web开发者都各自兴奋了一把:native的移动开发者想的比较多的估计是Facebook的那句:“learn once, write everywhere”,而web开发者兴奋的估计是,不需要学习iOS那陌生的OC或者swift语言,用自己熟悉的javascript语言就可以开发原生的移动APP了。那么新推出的react native 能否承载的了两大阵营的开发者的期待了。本人及同事对react native做了一段时间的调研,心中渐渐有了自己的答案:
本文假定读者熟悉iOS APP开发,但对web前端开发的知识匮乏(如本人一样),在此基础上试图讲清楚react native 到底是什么;怎么用;以及当前是否值得使用。
1.React Native 是什么?
首先,react native 到底是个什么东东:它是Facebook开源的一套框架,其目的在于使用JavaScript语言编写iOS native的控件。更直白的讲就是,你用JS(JavaScript,下同)写的代码通过react-native lib桥接到Xcode中写的标准iOS程序中。在JS程序中,开发者可以使用react native定义的一套和cocoa touch中UI控件类等价的类,来完成UI层的开发工作,Xcode 编译器 会利用 react-native lib将JS写的代码编译成iOS原生的UI组件,下图展示了利用Xcode的视图调试功能展示了JS代码编译的结果,可以看到,这些JS语言最终确实是被编译成了UIView等对象,而不是H5界面经常使用的webview,有了这个认识之后,我们对react native就不在那么陌生了。
综上,你可以认为react native是一套能够让你使用Javascript而不是传统的Objective-C或者Swift,编写iOS APP 界面逻辑(MVC框架中的V层)的框架。使用过swift得同学甚至还会觉得JS和swift的语法上又不少雷同的地方(好吧,swift五仁月饼果然名不虚传),比如定义一个变量都是用关键字var。这套框架最大的亮点有俩:
可以直接按照Facebook 官网搭建文档的指导来搭建React Native 开发环境,基本上没有坑,能够很迅速的完成。
除了运行环境的创建,还需要编写 JavaScript 代码的环境,Xcode 并非是最好的工具!Javascript 好用的编辑器网上很多,比如Sublime Text、atom等。
3. React Native 技术构成
3.1基本构成元素
React Native 的库同时包含了OC代码和javascript 代码,由这两种代码共同提供了一套用于构建界面UI系统的元素,包括但不限于:
OC传统UI组件;
OC上的手势识别及事件响应系统(如TouchableHighlight);
基于流的布局系统;
翻阅Facebook官网上的API文档,可以发现它基本上是实现了Cocoa Touch 框架上的最常用的UI控件:
基本上我们用JS写代码也是使用这些基本组件来构建我们的UI界面的。除此之外,你也可以在OC中,定制自己的模块,通过桥接的方式来在React Native 中使用。
3.2 React Native 中的事件响应系统
本地APP 和web 端的最大区别就是本地APP有着完美的事件响应系统,用户能够获得更好的用户体验。在React Native中也提供了一套事件响应系统,扒一扒React Native 的源码(在 ResponderEventPlugin.js 文件中),能够窥到React native事件响应的基本流程:
可见React native 的响应系统和Cocoa touch类似,一个view 如果想要对事件作出响应,它只需要实现函数:
View.props.onStartShouldSetResponder: (evt) =& true, - 当前view是否想作为touch 的响应者?
View.props.onMoveShouldSetResponder: (evt) =& true, - 当前view是否想作为move 事件 的响应者?
如果返回true,尝试要变成第一响应者,那么下面两个函数中的一个会被调用:
View.props.onResponderGrant: (evt) =& {} -当前view是第一响应者,在这里展示响应的交互效果(如背景色变化等)和事件触发的其他逻辑;
View.props.onResponderReject: (evt) =& {} - 其他view是第一响应者;
考虑到响应系统的复杂性,React Native 在对事件响应封装的基础上实现了一些抽象类,如类似Cocoa Touch 中UIButton 的 TouchbleHighlight,你可以向使用view一样将它放到你希望有交互效果的地方。我们通过下图来看看如何使用TouchbleHighlight:
3.3 React native UI更新逻辑
在Cocoa Touch 系统中,UI更新是典型的MVC模式:Controller 通过数据的变更,来更新view层的展示,但在React Native 中却大相径庭:React Native 通过状态机的机制来驱动整个view层的更新。在开始介绍这一块之前不得不得先说一下React Native 的渲染方式:
从之前图片中给出的代码片段中读者也能窥出这种构建页面的方法和HTML语言很像:通过标签系统构建出分层的页面逻辑(父子关系),布局代码则采用CSS 的方式通过单独的代码来控制,这样显示的将业务逻辑和布局逻辑分开,使得整个代码层次逻辑更清晰。
在React Native中,整个UI都是一个component树:前面我们提到的ListView等UI组建都是一种具体的component,React Native通过将component树编译成一个virtual-DOM:虚拟文档对象模型,熟悉HTML的读者可能对于这个DOM非常熟悉,没错,就是那个DOM,整个的UI的关系可以通过这个virtual-DOM很清晰的体现,而且更重要的是,React Native的UI更新逻辑也是依赖于这个树来实现的:我们知道,笼统的讲,一个页面的更新,肯定是由数据的变更来驱动的,比如网络数据的更新或者是用户触摸导致的touch事件的发生(以及后续业务逻辑的跟进),那么如何将这些数据的变更和界面的刷新相绑定呢?如何知道哪一块的数据变更后需要刷新哪一块的UI呢?要知道每次数据更新都重绘整个界面实在是一个吃力不讨好的事情:不仅你的APP处于一种高负载运行状态,而且用户体验也不好。React Native很巧妙的通过用户提供的state变量维着一个状态机,通过将这个状态机来驱动virtual DOM树的UI更新,如下图所示:
设计好了state信息之后,React Native会根据代码逻辑计算出那一块的DOM组件需要进行更新,整个过程不需要开发者来主动的干预,开发者只需要建立好state系统,并根据数据变化来维护state信息即可,React Native会在后台为你做好这一切。
那么一个component 中的state 是什么呢?其实就是一个属性,比如一个bool值或者数组或者任何其他JS支持的类型。一个component对象其实是包含了两种类型的属性的:property和state,前者主要是一些固定值的属性,后者则是那些数据会发生变化,并且这种变化会导致界面某一部分重绘的属性,比如列表页里面的数据源等,如何区分一个属性是应该被归类为property还是state,Facebook 的 React 官网文档:think in react上有详细的介绍,这里就不再赘述。
3.4 React Native的通信机制
在讲诉具体的通信原理之前,我们首先来看一下,在代码实现上是怎样的。我们这里说的通信,很大程度上指的是我们用javascript写的模块和OC写的本地模块直接能否互相调用,如下图所示:
在具体深入细节之前,先想一下在纯OC代码中,如果一个类对象想直调用另一个类对象的简单情况,那么主调用模块必须要知道的是:
我们可以将React Native里用JS代码写的模块和OC写的本地模块看作是两个互相陌生的城市,那么很显然,这两个城市之间的人要想有效的沟通,必须要彼此有一张对方城市的地图才行。那么在React Native世界里,这两张地图就是模块配置表,它看上去大概是酱紫滴(一下部分资料来源于Bang’s blog):
既然双方的通信可以通过模块配置表来解决,那么现在问题就简化为:如何向编译和运行时系统提供这张表了,为方便进一步分析,这两我们将通信氛围JS模块调用OC本地模块和OC本地模块调用JS模块两部分进行讨论:
3.4.1 JS模块调用OC本地模块
一个OC模块也即OC写的一个普通的类,在默认情况下是无法被Javascript 运行时系统捕获并进而被调用的,它必须要向编译及运行时系统提供or注册它自己,并暴露出自己哪些属性想被暴露出去,哪些方法可以被调用。诀窍就在于:
#import "RCTBridgeModule.h"
@interface CalendarManager : NSObject &RCTBridgeModule&
@implementation CalendarManager
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
主调模块JS中得调用方式如下:
var CalendarManager = require('NativeModules').CalendarM
CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey');
暴露出去的方法支持一下参数类型:
string (NSString)
number (NSInteger, float, double, CGFloat, NSNumber)
boolean (BOOL, NSNumber)
array (NSArray) of any types from this list
map (NSDictionary) with string keys and values of any type from this list
function (RCTResponseSenderBlock)
没错!就是这么简单! Awesome, isn’t it ? ^_^
除此之外,对于OC模块想暴露给JS模块的参数,可以通过constantsToExport方法提供,该方法返回的是一个字典,示例代码如下:
In OC模块:
- (NSDictionary *)constantsToExport
return @{ @"firstDayOfTheWeek": @"Monday" };
在JS模块中可以直接获取该函数返回的参数:
console.log(CalendarManager.firstDayOfTheWeek)
3.4.2 OC本地模块调用JS模块
OB本地的类对象要想调用JS模块里面的方法,也必须首先遵循3.4.1中提到的RCTBridgeModel协议,编译器创建的模块配置表除了有上述OC的模块remoteModules外,还保存了JS模块localModules。RCTBridgeModel 协议中提供了一个RCTBridge属性对象,该对象提供了访问JS模块的方法,代码如下:
* This method is used to call functions in the JavaScript application context.
* It is primarily intended for use by modules that require two-way communication
* with the JavaScript code.
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)
除了这种直接的调用方式之外,FacebookFacebook React Native 官网还提供了一种间接实现JS模块调用的方法,即通过RCTEventDispatcher,以发送和接收消息的方式实现调用,其原理图如下:
具体实现代码如下:
首先,在OC本地代码中发送通知EventReminder:
#import "RCTBridge.h"
#import "RCTEventDispatcher.h"
@implementation CalendarManager
@synthesize bridge = _
- (void)calendarEventReminderReceived:(NSNotification *)notification
NSString *eventName = notification.userInfo[@"name"];
[self.bridge.eventDispatcher sendAppEventWithName:@"EventReminder"
body:@{@"name": eventName}];
其次,在JS模块中监听EventReminder通知,并添加相应的通知响应函数:
var subscription = DeviceEventEmitter.addListener(
'EventReminder',
(reminder) =& console.log(reminder.name)
// Don't forget to unsubscribe, typically in componentWillUnmount
subscription.remove();
通过接收、发送通知的方式可以降低OC模块和JS模块的耦合度,而这种方式的实现同样是通过RCTBridge的直接调用方式来实现的,通过查看RCTEventDispatcher中发送通知的sendDeviceEventWithName的源码实现即可发现:
- (void)sendDeviceEventWithName:(NSString *)name body:(id)body
[_bridge enqueueJSCall:@"RCTDeviceEventEmitter.emit"
args:body ? @[name, body] : @[name]];
综上,我们可以认为,OC模块直接调用JS模块的通信方式主要通过RCTBridgeModel协议中的RCTBridge的sendDeviceEventWithName对象来实现,除了直接调用该方法的方式,还可以采用通知的方式间接调用(从FacebookFacebook React Native 官网上仅介绍了通知的方式,也许能看出这是Facebook推荐的方式,至于具体的使用,还需开发者依情境便宜行事)。
3.4.3 React Native 的通信机制总结
通过以上两节代码演示,我们能够很快的实现OC模块和JS模块的双路通信,基本上,无论是OC调JS还是JS调OC,其依赖的核心就是双侧模块提供的模块配置表(remote and local),至于其详细实现原理,其参看Bang’s blog,这里不再详述。
3.5. React Native 的UI布局机制
无论是web页面还是Native的本地页面,在开发中UI布局都是很重要的一环,能否兼容多重尺寸的页面、设备,是开发者面临的首要问题。随着苹果iPhone手机屏幕尺寸的越来越多样化(目前至少有iphone4s/5s/6/6plus四种尺寸了),苹果也越来越趋向于将UI布局重任放在了autolayout上面了,autolayout是一种典型的相对布局方法,开发者通过可视化编辑环境xib或者storyboard对UI组件添加约束,运行时系统通过自动布局引擎,根据实际的屏幕尺寸,计算出UI中每个控件的Frame信息,从而实现UI的布局。
和Cocoa Touch 不同的是,React Native 在UI布局上采用了一个完全不同的系统: HTML CSS,也就目前主流网页的流式布局方式,开发者可以将每一个的布局信息写入单独的style表中,将布局和业务逻辑分析,开发者使用HTML CSS的语法完成布局信息:
var styles = StyleSheet.create({
scrollView: {
backgroundColor: '#6A85B1',
height: 300,
margin: 7,
padding: 5,
alignItems: 'center',
backgroundColor: '#eaeaea',
borderRadius: 3,
buttonContents: {
flexDirection: 'row',
width: 64,
height: 64,
width: 64,
height: 64,
除了使用标准的HTML CSS方式进行布局,React Native还支持Flexbox模块的布局方式,根据其官网说明,Flexbox Layout module旨在提供一种更高效的方式来布局,以动态的决定在一个container中的子项的对其、居中、间隔甚至是尺寸大小的方式。
FlexBox布局对象只有两类:容器(container)和容器内的子项(item),如下图所示:
对于容器和子项分别有六七个布局属性关键字,罗列如下:
应用于Container的属性:
flex-direction
justify-content
align-items
align-content
应用于Item的属性:
flex-shrink
flex-basis
align-self
CSS中使用FlexBox只需要直接添加相应的关键字即可,如下代码所示,具体每一种布局关键字的意义可以通过这篇文章获取:
.flex-container {
/* We first create a flex layout context */
/* Then we define the flow direction and if we allow the items to wrap
* Remember this is the same as:
* flex-direction:
* flex-wrap:
flex-flow:
/* Then we define how is distributed the remaining space */
justify-content: space-around;
HTML CSS Style的布局方式相对于iOS 的自动布局方式,其动态性更好,但只能通过纯代码的方式来写布局,着实让人有些痛苦,而且对于广大没有web前端开发经验的iOS移动端猿猿们来说,CSS的布局方式初一上手,还是觉得有些陌生:基本上你要换一种思维方式才能考虑清楚具体的布局细节,而且对于更复杂的动态的场景,这种布局方式可能更难以实现和维护。
4. 目前,使用React Native的时机是否成熟
在React Native大热的同时,我们要谨慎的探讨一下使用React Native 的时机是否成熟这个问题。调研的这一段时间,我们发现有一下几点值得注意:
4.1 JS模块和OC模块的数据交互只能通过字典(dictionary)传递
字典在OC模块中是一种比较松散的数据结构,如果考虑使用React Native负责UI界面的绘制工作,OC模块负责数据的处理,那么二者的交互载体只能是字典。OC定义的model类对象(如使用core data时创建的model 对象)无法直接传递给JS模块使用,还必须要提前转为字典才行,这无疑多了一层处理逻辑,势必会带来一些潜在的风险。
以使用CoreData存储数据为例,我们的整个数据层的交互将是这样子的:
这种只能通过字典来传值的限制,就使得我们没法直接将OC模块中的数据对象直接作为JS模块里面驱动页面更新的state属性。我们将不得不添加一个中间层来转换数据的这种变化已映射到JS模块里驱动UI层的更新。
4.2 React Native的learn once,write everywhere 的实现还有待时日
Facebook在力推React Native的时候强调它的最大特点是:Learn once, write where. 但目前的实际情况是,React Native Android 预计2015年10月才发布,这对希望三端(Web/iOS/Android)架构一致的用户而言也算个风险。而且细看React Native iOS 框架里面,还有很多和iOS本地模块紧密耦合的模块,比如以iOS结尾的若干component都是iOS才有的,使用了这些模块的代码,将来想直接在Android上运行恐怕是不可能的事情,那么React Native在这一点上离真正的跨平台还有不少路要走:
**COMPONENTS**
ActivityIndicatorIOS
DatePickerIOS
NavigatorIOS
ScrollView
TabBarIOS.Item
4.3 React Native 中Listview 性能问题
在github的React Native有一个issue格外让人担忧: ListView renders all rows? 其中有几个评论揭示了这样一个事实:React Native的ListView可能一次性的渲染了所有的rows(cells):
@ide I'm a noob at instruments profiler... So here's brief summary from me taking a look at it.
cpu profile looks like most of the time is spent here (recursing through subviews), in RCTView.m :
- (void)react_updateClippedSubviewsWithClipRect:(CGRect)clipRect relativeToView:(UIView *)clipView
In memory profile major causes of persisted memory (645 MB total) are:
VM: CG Graphics Data (410 MB)
VM: CoreAnimation (141 MB)
VM: JS Garbage Collector (61 MB)
Unmount the ListView component, and total persisted memory drops to 74 MB total:
VM: JS Garbage Collector 58 MB
VM: CoreUI image data
Kureev commented 24 days ago
It's totally insane: my iPhone 5c crashes after 700 list items. If I'm going to write a chat - it's blocking for me.
Also I got a lot of "Cannot find single active touch"
samfriend commented 14 days ago
my & ListView pagingEnabled={true} onEndReached={this.loadAnotherFiftyArticles} &
50 rows/pages of & Image / & & Title/ & & Description/ &
3rd Load append (total 150)
Received memory warning
Received memory warning
Received memory warning
Crash Physical iPhone 6 Plus
Also I got a lot of "Cannot find single active touch" time to time
为了证明网友们的担忧,这里我们用Xcode的view透视工具看了一下React Native 官网提供的Demo: UIExplorer的ListView,看看到底是否是一次性重绘了所有的rows,下图展示了这个Demo运行时的界面:
然后我们使用Xcode 的 Debug View Hierarchy来查看这个试图的界面层级结构,结果让我们触目惊心:它果然对所有的row进行了绘制!
再看一个scrollView的情况,好吧,看完我整个人都不好了:
如果情况真是这样,那就这一条就足有让我们有理由选择放弃使用React Native了,至少暂时是!
4.4 React Native 的UI布局系统不尽如人意
我们知道,React Native采用的web前端的HTML CSS 也即流式布局。整个UI界面都是通过树形结构构建起来,布局也是基于此,而且需要全手动打造。使用过纯代码的方式写iOS 上的autolayout 布局的童鞋相比一定被这种非可视化的布局方式深深刺痛吧:你必须要在脑海里将所有的UI展示效果转换成为纷繁复杂的布局约束。
autolayout可视化的布局是React Native所最欠缺的
采用CSS布局的另一个劣势是,相比较于传统的Native 布局方式,精确性控制的不是很好,最终布局效果可能和设计师的初衷相差甚远。在自动布局autolayout 中我们可以通过sizeclass针对横竖屏做定制化的布局工作,但是使用CSS目前来看还没法实现这种方式。
通过上诉的分析,我们发现React Native在性能、开发便利性等方面还存在很大的不足。目前来看,它还没实现它所倡导的“Learn Once, Write Everywhere”的目标,但带来的问题却不少,别的不说,单就ListView的性能问题就是一个最大的瓶颈。React Native还处在一个初期摸索阶段,它的下一阶段发展如何,还要看它的老东家Facebook接下来的动作。因此,笔者建议目前已有的开发项目中不要冒险采用React Native 技术,保持技术跟进即可。
相关搜索:
相关阅读:
相关频道:
Android教程最近更新欢迎界面搭建完毕,我们接下来需要做的就是搭建应用程序的主体框架啦。首先我们看一下首页的截图:
  从图中看到,我将首页分为了三部分:用黑色矩形表示的头部,绿色表示的内容和红色表示的底部。
  下面我们需要解决的是红色部分。在iOS中想要实现这个效果很简单,只需要使用react-native提供的组件就可以啦。但是却没有提供Android的类似的组件。为了解决这个问题,我想了三种解决方式,第一种是分别为iOS和Android搭建界面,第二种是使用他人封装好的组件,第三种就是自己写一个。最后我选择了第三种,原因是如果使用第一种方式android的界面还是自己搭,而第二种我找了几个,发现样式的可定制性参差不齐,用了不知道会出什么事情。
  从图片中我们很容易看出它充当的是页面跳转的作用,也可以认为它充当了一个路由的作用。我们点击不同的按钮跳转到相应的界面。
  MainRoute.js
1 'use strict'
3 import React from 'react-native'
4 import Icon from 'react-native-vector-icons/FontAwesome'
5 import MainScreen from './MainScreen'
6 import LoginScreen from './UserLRScreen/LoginScreen'
7 import RecommendScreen from './RecommendScreen'
8 import SettingScreen from './SettingScreen'
9 var {Platform} = React
StyleSheet,
TouchableOpacity,
16 } = React
18 const COLOR = ['gray', '#ffffff']
20 class MainRoute ponent {
static propTypes = {
navigator: PropTypes.object,
graphics: PropTypes.object
constructor (props) {
super(props)
var navigator = props.navigator
this.state = {
choice: 1,
screen: &MainScreen navigator={navigator} /&
render () {
&View style={styles.container}&
&View style={styles.viewShow}&
{this.state.screen}
&View style={styles.bottom}&
&TouchableOpacity style={styles.bottomButton} activeOpacity={0.1} onPress ={() =& this.tabColor(1)}&
&Icon name='star' size={25} style={[styles.Icon, {color: this.state.choice === 1 ? COLOR[1] : COLOR[0]}]}/&
&/TouchableOpacity&
&TouchableOpacity style={styles.bottomButton} onPress ={() =& this.tabColor(2)} &
&Icon name='compass' size={25} style={[styles.Icon, {color: this.state.choice === 2 ? COLOR[1] : COLOR[0]}]}/&
&/TouchableOpacity&
&TouchableOpacity style={styles.bottomButton} onPress ={() =& this.tabColor(3)}&
&Icon name='bell' size={25} style={[styles.Icon, {color: this.state.choice === 3 ? COLOR[1] : COLOR[0]}]}/&
&/TouchableOpacity&
&TouchableOpacity style={styles.bottomButton} onPress ={() =& this.tabColor(4)}&
&Icon name='cog' size={25} style={[styles.Icon, {color: this.state.choice === 4 ? COLOR[1] : COLOR[0]}]}/&
&/TouchableOpacity&
tabColor (num) {
var navigator = this.props.navigator
if (num === 1) {
this.setState({choice: 1, screen: &MainScreen navigator={navigator} /&})
if (num === 2) {
this.setState({choice: 2, screen: &RecommendScreen navigator={navigator} /&})
if (num === 3) {
this.setState({choice: 3, screen: &LoginScreen navigator={navigator} /&})
if (num === 4) {
this.setState({choice: 4, screen: &SettingScreen navigator={navigator} /&})
73 var styles = StyleSheet.create({
container: {
marginTop: (Platform.OS === 'ios') ? 20 : 0,
flexDirection: 'column',
backgroundColor: 'black'
viewShow: {
content: {
height: 50,
backgroundColor: 'black',
flexDirection: 'row'
buttonImage: {
height: 30,
bottomButton: {
alignItems: 'center',
justifyContent: 'center'
color: 'white'
105 module.exports = MainRoute
上面的代码如果不懂也没有关系,我会一点点解释。
代码:import Icon from 'react-native-vector-icons/FontAwesome'
这是我引用的一个字体图标库,网址:,Android和iOS使用方式里面介绍的很详细
这里我们会有很多Icon可以去选择。进去选一个自己认为合适的。
使用方式:
&Icon name='compass' size={25} /&//name就是你选择的Icon的名称
import MainScreen from './MainScreen'
import LoginScreen from './UserLRScreen/LoginScreen'
import RecommendScreen from './RecommendScreen'
import SettingScreen from './SettingScreen'
这是点击按钮需要进入的界面,这里我们还没有实现,可以搭建一个最简单的界面用于预览。
三、navigator
这是一个界面跳转组件,这个组件很重要,后面会有详细介绍。
四、TouchableOpacity
使用TouchableOpacity嵌套的组件,当我们用手点击的时候,会有一个点击动画。并且使用onPress实现点击效果:
tabColor (num) {
var navigator = this.props.navigator
if (num === 1) {
this.setState({choice: 1, screen: &MainScreen navigator={navigator} /&})
if (num === 2) {
this.setState({choice: 2, screen: &RecommendScreen navigator={navigator} /&})
if (num === 3) {
this.setState({choice: 3, screen: &LoginScreen navigator={navigator} /&})
if (num === 4) {
this.setState({choice: 4, screen: &SettingScreen navigator={navigator} /&})
我们根据点击的哪一个按钮跳转到相应的界面。
阅读(...) 评论()}

我要回帖

更多关于 html 精美登陆界面 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信