一. 什么是RN?
1. Build native mobile apps using JavaScript and React
2. A React Native app is a real mobile app
一千个人用就有一千种解释,本人概而言之为:React-Native利用web应用和Native的优势,用JS来实现移动端的应用。利用React的原生UI组件代替DOM的渲染,实现了一种只用一种开发语言便能高效的开发出一款与平台无关的app。
详细请参考官方文档:https://facebook.github.io/react-native/
二. 相关技术名词解释。
想更好的学习接下来的内容,一些必要的技术名词是需要事先弄明白的。免得一头雾水。
三. RN的原理
对于菜鸟而言,对于这么深奥的原理,我一般会细化。
整体架构:
个人喜欢对比性分析问题,喜欢细化性分析问题,所以RN的原理我主要分了以下几个步骤来学习理解:
第一步:ReactNative的渲染机制。
在这里引用一个Virtual Dom的概念。顾名思义,这是对DOM的一个虚拟。是一个纯js写的一个伪DOM的结构,主要是运用一种diff的算法,高效完成局部数据刷新。网页实际上都是解析成dom的格式被加载渲染,但是每次渲染都是dom数据的重载,而virtual dom则是实现了部分重新加载这样就大大提高了高效性。
说到这,就会提到React框架,所谓的React框架其实就是一套简洁高效绘制DOM的框架,而这个框架的高效实现就是基于Virtual DOM。之所以快还有一个原因Virtual DOM是运行在内存里的。
综合以上,宏观上我们似乎对React有了那么一丁点的了解,不妨趁热打铁,来瞅瞅源码对DOM的实现:
对比性学习效率会更高,DOM的结构大家都不陌生,第一想到的便是Element,没错在ReactNative的源码里有这两个很相似的玩意:ReactElement和ReactClass
首先回顾下DOM的结构实例:
{type:'button',
props: { className:'button button-blue',
children: {
type:'b',
props: {
children:'OK!'}
}
}}
而针对于React而言,我们称之为Component Element:
classButtonextendsReact.Component {
render() {
const{ children,color } =this.props;
return{
type:'button',
props: {
className:'button button-'+ color,
children: {
type:'b',
props: {
children: children
}
// Component Elements
{
type: Button,
props: {
color:'blue',
children:'OK!'
}
看完他和DOM的结构不同,再来看看我们怎么用:
看起来好简单,就是js随便搞搞,但是问题来了,怎么没和dom结构扯上任何关系的赶脚?看看人家如何运行的就明白了:
进入到ReactElement的源码当中其实就是一个js:
var ReactElement * function(type,key,ref,self,source,owner,props);
可以看的,父元素的props对象的一个属性children指向了刚新建的eleChildren元素;我们用对象的形式表示出来就是:
{
$$typeof: Symbol(react.element),//ReactElement的唯一标识
_owner: null,
key: undefined,//唯一标识
props: {//属性
children: {
$$typeof: Symbol(react.element),
_owner: null,
key: undefined,
props: {
children: "look~",
id: "reactChild"
},
ref: undefined,
type: "p"
},
id: "text",
onclick:"hello"//指向hello方法的指针
},
ref: undefined,//对DOM的引用
type: "div"//标签类型
}
这样的话ReactElement的结构就很清楚了,React正是通过这种对DOM的抽象,再根据不同的ReactElement生产不同的组件Component,然后递归渲染;其中React.render()便是处理事件绑定的过程。
以上都是说的ReactElement,下面我们聊聊ReactClass。
顾名思义,看见class就知道肯定和类脱不了关系。你很聪明,它确实就是React Component整出来的类或者是属性。说到这有些小伙伴可能不太懂Component这个东东了,玩过html吗?很好,你可以简单的理解它就是html的任何一个tag属性,比如div,li等等。几乎包含了html的所有标签。不过需要注意的是每一个component要包含一个render使用。这个也不难理解,这就是为了创建一个element。因为毕竟最终的dom都是以element组成的。吻合了dom的渲染。
通过上图可以看出这是一个递归调用的过程,最终是以一个element结束。
Component Elements实例
class Button extends React.Component{render() {
const { children, color } =this.props;return{type:'button',
props: { className:'button button-'+ color,
children: {
type:'b',
props: {
children: children
} } } }; }}
// Component Elements
{type:
Button,
props: { color:'blue', children:'OK!'}
}
1. 调用 React.render 方法,将我们的 element 根虚拟节点渲染到 container 元素中。element 可以是一个字符串文本元素,也可以是如上介绍的 ReactElement 。
2. 根据 element 的类型不同,分别实例化 ReactDOMTextComponent , ReactDOMComponent ,
3. ReactCompositeComponent 类。这些类用来管理 ReactElement ,负责将不同的 ReactElement转化成DOM,并更新DOM。
4. ReactCompositeComponent 实例调用 mountComponent 方法后内部调用 render 方法,返回了 DOM Elements 。然后递归。
第二步:ReactNative通信机制
在说到底层的通信机制前,先来了解下宏观上js和nativemodule上是怎么玩的。官网上明确提出了三种方式,callback,promises和event以及onActivityResult。
callback:例子很简单,比如native层对布局发生了改变,callback就返回给js层,返回去的数据包括布局所需要的属性值。
promises:个人觉得和callback的用法神相似,只不过是pormises返回给js的是一个对象。
event和onActityResult就很熟悉了在这里就不多讲了,大家一看便知。个人建议学习api还是照官网来。
well,接下来我们透过现象看本质,实际上js和native的通信到底是干了什么见不得人的勾搭。
由于本人是做android的,所以直接拿android说事。首先盗个图:
通过上图不难看出,通信的核心部分就是Bridge和Webkit这两块东西了。根据通信方向是双向的,和cs的模式非常相似,一端发问一端回答,而这里的客户端切记是native层发出的。
一说到底层大家就慌了,有种不知如何下手的赶脚,别怕,先从入口来吧,总归会发现些许的蛛丝马迹。拿我的demo为例,先从MainActivity开始:
好简洁神马都没,那就直接进入ReactActivity里面看看。看到了一个关键:
private final ReactActivityDelegate mDelegate;
ReactActivity被封装的也很好并看不出入口的嫌疑,所以我锁定了这个代理,进去瞅瞅。
我们熟悉的onCreate方法终于出现了,loadApp让我们眼前一亮,继续跟踪:
终于等到你,startReactApplicaition这个方法说明了一切,别太高兴,我们刚刚找到个入口而已。看看参数吧,细枝末叶的就不说了,这个ReactInstanceManager貌似很重要,manager嘛肯定来头不小,进去一看果真如此:
一切的一切越来越明朗了,这个builder有那么多熟悉的玩意,瞬间想起了我建立的nativemodule啊,package啊以及传说中的js啊,好像都在这里同时出现了,赶紧来看看demo之间他们的联系。
TestMoudle:两个地被用到,第一处是在MyReactNativePackage里面:
@Override
publicListcreateNativeModules(ReactApplicationContext context) {
List modules =newArrayList<>();
modules.add(newTestModule(context));
returnmodules;
}
第二在index.android.js里面:
export default class FirstReactApp extends Component{
render() {
return(
toast for short
this.onClick()}>
);}onClick() {
NativeModules.TestModule.callNativeMethod("zsfsdgfhfgh");}}
MyReactNativePackage: 好家伙在MainApplicaiton里面呢。
一层层往后剥,不管你们懂没懂,看到这我大概就懂了。先小小总结一下,消化下再继续。
module像是一个javabean,reactpackage相当于一个容器囊括的各色各样的module,最终又全部塞进Applicaiton里面送到底层。同时也把入口的js给传过去了,我不睁眼说瞎话,看证据:
从createReactInstanceManager
这个方法看到:
builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
进入到getBundleAssetName()这个方法内:
protected@Nullable
StringgetBundleAssetName() {return"index.android.bundle";
}
备注:明白人都知道index.android.js编译之后都是和index.android.bundle对应的。
后面就是JSBundleLoader处理js的操作。它的主要作用是去加载JSBundle。
回到startApplication方法里找到真正执行的方法:
越陷越深,突然发现了执行js的这个玩意:
有种恍然大悟的感觉,configmap,原来人家是有协议的。换句话说是要进行一一对应,网上说的真对,js和native之间是要相互注册进行翻译识别的。在Java层与Js层的bridge分别存有相同一份模块配置表,Java与Js互相通信时,通过bridge里的配置表将所调用模块方法转为{moduleID,methodID,args}的形式传递给处理层,处理层通过bridge的模块配置表找到对应的方法执行,如果有callback,则回传给调用层。
讲到这大概的工作原理是通了,下面就真枪实弹来一发吧。
四. RN的环境搭建。
环境准备
step1:下载最新的node.js
官网下载地址:https://nodejs.org/en/
step2: 配置node.js的环境变量,例如:
验证node.js是否安装成功:
step3:AS安装配置就不说了,但是有一点sdk必须是23.0.1的,react默认支持这个版本。
step4:安装React-Native. 用npm安装。npm install -g react-native-cli(前提是先下载reactnative)
step5:安装成功后创建项目:react-native init XXX(your project's name)
备注:有关RN开发环境搭建的东西就不详细介绍,以上步骤足以开发一个rn项目,如有需要可以下载模拟器。
五. RN的demo
1. 如何在AS里面导入一个不冗余的RN项目
通过react-native init xxx后的文件,直接导入android这个文件夹即可。
2. 可能会遇到的问题
问题一:出现不能load index.android.js的问题
原因是:导入到as要想正常的加载运行react项目必须有个build的工具进行对js的桥接,这个就是index.android.bundle。
解决方案:
要在main的目录文件先创建一个assets文件夹然后在项目根目录下执行:
问题二,导入到as的android项目没有把index.android.js导进来,怎么办?
原因是:index.android.js是在整个react目录下并不属于具体的android单独的项目,因此没法包含进来。
解决方案一,把一下标红的模块全部复制到android目录下,然后修改所有js模块下的目录引用,将../../改成../(方法不可取,工作量很大,一处修改不好都会造成项目运行失败)
解决方案二,as只负责下react的纯代码逻辑,js的东西还是在外面单独写吧。(虽然看起来和项目达不到是是一致性,但是是可取的,接受代码分离开发的模式,毕竟还是人家rn还是前端的,除非你不用as开发)
问题三,修改了index.android.js代码,可是并没有起作用。
原因是:上面就提到过,index.android.js虽然是关键,但是index.android.bundle却是纽带,他需要把js加工一层。不经过加工的js怎么改都是没用的,as不支持自动更新。
解决方案:在项目所在的根目录下重新生成index.android.bundle文件:
F:\myTestProject\FirstReactApp>react-native bundle --platform android --dev fals
e --entry-file index.android.js --bundle-output android/app/src/main/assets/inde
x.android.bundle --assets-dest android/app/src/main/res/
备注:目前我只发现可以这样,如有更好的解决方案,欢迎留言。
3. demo详解
通过对index.android.js的修改玩转各种基本属性。
A. 有关神马的html标签如何使用,就不啰嗦了,很简单。我总结的规则:想使用什么标签就得先import什么标签。render是起点,return是关键。事实上return回来的东西是要jsx解析的。所以return里面可以写任意类似的html标签,若想定义属性,可以在render里,return外定义。
B. props属性
再没开始props这个属性前,我们先看下代码结构default class就类似于我们入口class,可以想象成java当中的main方法,其他的class,可以任意定义,然后嵌套使用和引用。而引用的方式却是组件式属性引用。‘aa’相当于一个变量名称,必须和引用一一对应,所以可以任意定义。效果图:
C. state属性。
上面讲到props属性感觉挺好用的,有种数据绑定的意思。但是真正起到数据绑定且在项目中举足轻重的还是state这个属性。比如server端的数据有更新,前端需要更新咋整?这个时候就需要state了。以下是个简单的例子:
这里的例子和官网的例子差不多,因为没有server端的数据,所以就整了计时器假装数据有更新。这个demo需要注意的点。第一,state必须在construct里面初始化,从代码中可以看到其实state也是一个props,你可以当成是props的升级。第二,数据更新的关键是setState方法。
D. Network
比较好的一点是,支持fetch,支持第三方网络请求库,也支持websocket。
Fetch
testFetchMethod() {
fetch('source address',
{method:'POST',
headers:{'Accept':'application/json','Content-Type':'application/json'},
body:JSON.stringify({
firstParam:'aaaa',
secondParams:'safdsfds'})});
}
第三方库(比如:XMLHttpRequest)
var request =new XMLHttpRequest();
request.onreadystatechange= (e) => {
if(request.readyState!==4) {
return;
}
if(request.status==400) {
console.log('success',request.responseText);
}else{
console.log("error");
}
request.open("GET","address");
request.send();
websocket
varws =newWebSocket('ws://host.com/path');
ws.onopen= () => {
// connection opened
ws.send('something');// send a message
};
ws.onmessage= (e) => {
// a message was received
console.log(e.data);
};
ws.onerror= (e) => {
// an error occurred
console.log(e.message);
};
ws.onclose= (e) => {
// connection closed
console.log(e.code,e.reason);
};
备注:以上只是个人觉得比较实用的属性。其他有需要可以研究api。
F:JS和Native通信的demo
其实在讲通信机制的时候已经说到了,大概的流程:
源码就不再贴了,说一下可能遇到的问题。
解决方案:在你的nativemodule里面一定要加入以下代码:
@Override
public booleancanOverrideExistingModule() {
//这里需要返回true
return true;
}
六. RN的优缺点
有关RN的优缺点,本人暂时先保留不说。别问我为什么,还没研究到一定的程度时,我没资格说!!
七. 总结
从一窍不通到对这个RN的学习,研究收货甚多。也感觉到了RN的精妙之处,这是第一篇,后续我会继续深究完善后续升级文档。喜欢的希望继续关注!