程序员Android技术知识

React Native深入研究(第一篇)

2017-07-07  本文已影响2153人  最有文化的码农

一. 什么是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的精妙之处,这是第一篇,后续我会继续深究完善后续升级文档。喜欢的希望继续关注!


上一篇下一篇

猜你喜欢

热点阅读