美文网首页程序员Android技术知识
React Native深入研究(第一篇)

React Native深入研究(第一篇)

作者: 最有文化的码农 | 来源:发表于2017-07-07 19:17 被阅读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的精妙之处,这是第一篇,后续我会继续深究完善后续升级文档。喜欢的希望继续关注!


    相关文章

      网友评论

        本文标题:React Native深入研究(第一篇)

        本文链接:https://www.haomeiwen.com/subject/misucxtx.html