一、React Native所做的工作
1)更新DOM
只更新不读取。
2)响应事件
响应事件,状态变化,告知渲染。
二、React Native的优点与缺点
1、优点:
1)最后生成原生应用,交互和性能优于Webview。
2)有标准组件,可以自由组合。
3)支持Native原生动画。
4)通过更新远端JS,直接更新APP。
5)缓解移动开发人员不足:跨平台,JavaScript用户群体更大。
2、缺点:
1)交互和性能比Native差。
2)扩展性弱于Web和Native。
三、集成使用
1、环境搭建:
1)安装node: brew install node
2)安装watchman: brew install --HEAD watchman
3)安装flow: brew install flow
4)安装react-native命令行工具: npm install -g react-native-cli
2、新建项目:
1)通过reactnative创建项目:react-native init Project
2)通过Project.xcodeproj运行项目
3)编辑index.ios.js文件修改实现视图代码
3、已有项目:
1)安装CocoaPods: sudo gem install cocoapods
2)解绑node:brew unlink node
3)安装io.js: brew install homebrew/versions/iojs
4)配置io.js:brew link iojs —force
5)建立Podfile文件
pod ‘React’
pod ‘React/RCTText’
在终端运行:pod install
6)在已有应用目录下建立React Native内容目录
mkdir ReactComponent
7)增加index.ios.js文件
‘use strict’
var React = require(‘react-native’);
var{
Text,
View
} = React;
var styles = React.StyleSheet.create({
container:{
flex: 1,
backgroundColor: ‘red’
}
});
class SimpleApp extends React.Component{
render(){
<View style = {styles.container}>
<Text>This is a simple application.</Text>
</View>
}
}
React.AppRegistry.registerComponent(‘SimpleApp’, () => SimpleApp);
8)创建RCTRootView视图
NSString *urlString = @“http://localhost:8081/index.ios.bundle”;
NSURL *jsCodeLocation = [NSURL URLWithString:urlString];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@“SimpleApp” launchOptions:nil];
9)启动服务器:
JS_DIR=pwd/ReactComponent; cd Pods/React; npm run start --root $JS_DIR
4、离线包:把JS文件打包到ipa。
终端进入应用根目录,运行react-native bundle命令。根目录的index.ios.js文件的配置信息将会写入/ios/main.jsbundle文件。
四、Node实现服务端API
1、启动Apache 服务器
sudo apachectl -k start
检测:输入命令curl 127.0.0.1,出现<html><body><h1>It works!</h1></body></html>
2、安装Express框架:npm install express
3、创建server目录:mkdir todoserver
4、在server目录下配置server:npm init
5、把Express集成到server:
npm install —save express
touch index.js
6、创建public目录:mkdir public
7、在public目录创建index.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Todos</title>
</head>
<body>
<hl>Todo</hl>
</body>
</html>
8、编辑index.js文件
var express = require('express');
var app = express();
app.get('/',function(request, response){
response.send('Hello World');
});
app.get('/todos',function(request, response){
var todos = ["Todo item 1","Todo item 2","Todo item 3"];
// response.redirect(301,'/parts');
response.send(todos);
});
app.get('/blocks',function(request, response){
var blocks = '<ul><li>Todo item 1</li><li>Todo item 2</li><li>Todo item 3</li><ul>';
response.send(blocks);
});
app.get('/sendfile',function(request, response){
response.sendFile(__dirname + '/public/index.html');
});
app.listen(3000, function(){
console.log('Listening on port 3000');
});
9、启动服务:node index.js
10、在终端输入命令curl -i http://localhost:3000/todos、curl -i http://localhost:3000/sendfile等,测试请求。
五、整体框架(从上到下,各个部分)
React应用组成:
‘use strict’ //加入严格模式
var React = require(‘react-native’); //引用ReactNative
1、标准组件:可嵌套组合
var {
AppRegistry,
Image,
ListView,
NavigatorIOS,
StyleSheet,
Text,
TextInput,
TouchableHighlight,
View,
} = React;
React.createClass 传入包含函数的JSON对象创建自定义组件。
2、自定义组件:
var GithubFinder = React.createClass({
render:function(){
return(
<View style = {styles.container}>
<Text></Text>
</View>
);
}
});
render方法:返回组件渲染的视图。return的根标签为单个<View>。
视图表示方式:可嵌套的标签,通过标签属性定义不同的渲染效果。
3、通过StyleSheet标准组件的create方法创建样式表。
var styles = StyleSheet.create({
container:{
flex:1,
justifyContent: ‘center’,
alignItems: ‘center’,
backgroundColor: ‘#F5FCFF’,
}
});
4、通过标准组件AppRegistry的registerComponent方法注册根组件。
AppRegistry.registerComponent(‘GithubFinder’, () => GithubFinder);
此行代码,一般放到React Native应用代码的最后一行。
{}表示变量
{{}}表示javascript对象
5、方法代理绑定
var GithubFinder = React.createClass({
render: function(){
return(
<View>
<TextInput onEndEditing = {this.onSearchChange}/>
</View>
);
},
onSearchChange: function(event: Object){
fetch(queryURL)
.then ({
this.setState({
dataSource: this.state.dataSource.cloneWithRows()
});
})
.done();
},
});
6、组件状态:存储组件数据
通过getInitialState方法初始化组件状态:
var GithubFinder = React.createClass({
getInitialState: function(event: Object){
return{
dataSource: new ListView.DataSource({
rowHasChanged: (row1,row2) => row1 !== row2,
}),
};
},
render: function(){
},
onSearchChange: function(event: Object){
},
});
7、在render方法中添加组件:
var GithubFinder = React.createClass({
render: function() {
var content;
if(this.state.dataSource.getRowCount() == 0){
content = <Text></Text>;
} else {
content = <ListView
ref=“listview”
dataSource={this.state.dataSource}
renderRow={this.renderRow}/>;
}
return (
<View>
{content}
</View>
);
},
renderRow: function(repo: Object){
return (
<View>
<Image />
<View>
<Text>{repo.name}</Text>
<Text>{repo.owner.login}</Text>
</View>
<View />
</View>
);
},
};
8、React Native导航:
var NavDemo = React.createClass({
onRightButtonPress: function(){
this.refs.nav.push({
title: ‘From Right’,
component: ForRightScene //跳转显示组件
*//如何带参数
})
},
render () {
return (
<NavigatorIOS ref=“nav”
initialRoute={{
component: HomeScene,//第一页面
title: ‘NavigatorIOS Demo’,
rightButtonTitle: ‘MORE!’,
onRightButtonPress: this.onRightButtonPress
}}/>
);
}
});
var HomeScene = React.createClass({
onPress(){
this.props.navigator.push({
title; ‘From TouchableHighlight’,
component: ForTouchScene
});
},
render(){
return (
<View>
<TouchableHighlight onPress={this.onPress}>
<Text>Something<Text/>
</TouchableHighlight>
</View>
);
}
});
var ForRightScene = React.createClass({
render(){
return (
<View><Text>From Right Button</Text></View>
);
}
});
var ForTouchScene = React.createClass({
render() {
return (
<View><Text>From TouchableHighlight</Text></View>
);
}
});
var styles = StyleSheet.create({
container:{},
scene:{}
});
AppRegitry.registerComponent(‘rctnavigator’, () => NavDemo);
NavigatorIOS有后退按钮,执行pop操作。
8、React组件数据流:
1)props(静态数据)
可以在挂载组件时设置,也可以调用setProps({})方法。通过this.props访问。通过PropTypes检查数据类型。通过getDefaultProps设置组件属性默认值。d
2)state(动态数据)
通过setState方法设置动态数据。
六、实现原理(过程细节)
1、React Native通信原理:
React Native 使用iOS自带JavaScriptCore作为JS解析引擎。当数据变化,React组件的Render方法返回虚拟DOM树。与上次DOM树对比,得到区别,批量实际更新需要变化的部分。
2、JS-OC通信:
1)普通OC调JS:webview 接口 stringByEvaluatingJavaScriptFromString 在当前context执行JS脚本并获取返回值,JS向OC传递信息。
JSValue //可以重点看看
2)React Native的OC调JS: OC定义模块方法,JS调用并衔接回调。
模块配置表:模块、方法信息
OC端/JS端各有bridge,保存同一份模块配置表,调用时把模块方法转为
JS -> JS Bridge -> OC Bridge -> OC
(1)取所有模块类
模块类实现RCTBridgeModule接口,通过runtime接口objc_getClassList或objc_copyClassList取出项目所有类,逐个判断是否实现RCTBridgeModule接口。RCTBridgeModuleClassesByModuleID()
(2)取模块里暴露给JS的方法:改二进制文件
代替方法前缀,使用编译属性__attribute__
宏 RCT_EXPORT()
#define RCT_EXPORT(JS_name) __attribute__((used, section(“__DATA,RCTExport”\))) static const char *__rct_export_entry__[] = {__func__, #JS_name}
用编译属性__attribute__给二进制文件新建section,__DATA数据段 RCTExport 加入当前方法名。
数据段多了RCTExport,内容为暴露给JS的方法,运行时获取。在RCTBridge RCTExportedMethodsByModuleID()获取这些内容,提取类名方法名。
整体模块类方法提取实现在RCTRemoteModulesConfig()。
(3)LinkMap
# Object files: //目标文件列表
# Sections: //段表 各段在可执行文件中的偏移位置大小
代码段:__TEXT //程序代码段机器码
数据段:__DATA //保存变量值
偏移位置、段大小、段类型、段名
__text 程序执行语句 __data全局变量局部静态变量 __bss未初始化全局变量 __cstring字符串常量
# Symbols: //每个文件(类)每个字段(方法)的位置占用空间
3、React Native 的JS调OC:
JS函数调用转ModuleID/MethodID -> callback转CallbackID -> OC根据ID拿到方法 -> 处理参数 -> 调用OC方法 -> 回调CallbackID -> JS通过CallbackID拿到callback执行。
1)JS端调用OC模块方法
2)调用分解为ModuleName/MethodName,arguments,交给MessageQueue。
在BatchedBridgeFactory.js 的_createBridgedModule里实现。
3)把JS callback 缓存到MessageQueue成员变量,用CallbackID代表Callback
ModuleName、MethodName通过MessageQueue的模块配置表转为ModuleID和MethodID
4)发生事件时,OC调JS,JS处理完后,把ModuleID、MethodID、CallbackID和参数返回给OC。
5)OC通过模块配置表拿到对应的模块和方法。在_handleRequestNumber:moduleID:methodID:params:方法里实现。
6)RCTModuleMethod处理JS传过来的参数。实现JS到OC的类型转换。通过NSInvocation动态调用OC模块方法。
7)OC模块方法调用完,执行block回调。
8)调用RCTModuleMethod生成的block。
9)block用CallbackID和block参数调JS的MessageQueue的invokeCallbackAndReturnFlushedQueue。
10)MessageQueue通过CallbackID找到JS Callback方法。
11)调用Callback方法,传递OC的参数,完成回调。
七、开发技术(工具、语言)
1)JSX
2)Webpack
3)Browserify
八、参考书籍
1)《React Native 用JavaScript开发移动应用》
2)《React 引领未来的用户界面开发框架》