2018-07-08 14:57:57 r122555 阅读数 763
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57520 人正在学习 去看看 李宁

前言

React Native的一大特点就是用户界面其实都是由原生组件所组成。JavaScript编写的React组件仅仅作为原生视图的抽象表现以及配置。所有React组件最后都会被渲染成原生UI组件。这使得我们可以使用熟悉的JavaScriptReact让用户界面开发变得很简单。

React Native还提供了许多暴露为NativeModules的库,来调用原生的API。这些方法可以让JavaScript调用,或者传播事件让JavaScript监听。原生模块函数从JavaScript端接收参数,经过桥接层后,参数被处理成恰当的类型并且可以直接使用。

本篇试图探究原生组件和原生模块的架构方式,以及如何开发它们。

原生组件

React Native开始渲染一个界面时,原生层会建立与该界面结构一样的镜像。桥接层把规则(组件属性)从React Native所描绘的UI层传递给原生层。最终在屏幕上看到的,就是React组件转译成相应原生组件的结果。以下我们将探究:
- ViewManager(“视图管理器”)如何管理原生UI组件;
- React组件属性如何传递给在原生层定义的函数并选择处理方式。

React Native中的JSX语法类似于我们所熟悉的React虚拟DOM,它就是用来编写设置原生视图的配置文件。

每一个JSX元素都与其所代表的原生组件的实例绑定到一起。两者总是与对方保持同步,React组件定义页面结构,原生UI组件就渲染出对应的UI结构。

React Native创建UI组件的过程概述:

React Native所创建的组件挂载之后,通知原生层根据页面结构绘制节点。UI组件的实例被创建。每个实例分配了一个标识符,并与JavaScript层共享。从此刻起,只要这些视图的属性发生了改变,或者将它们从屏幕上移除,这些改变集合会根据它们的身份(组件ID)和改变内容(任意属性)通知给原生层,原生层就会按照JavaScript层传来的信息调整布局。

剖析原生组件

一个原生组件主要由两部分组成:ViewManager ,以及实际的UI组件。

ViewManager扮演的角色负责连接React组件以及该组件代表的原生UI组件实例。ViewManager 是单例模式,对于指定的组件类型只有唯一一个视图管理器,它管理着指定类型的所有组件。

React组件挂载之后就会触发它的render方法,并返回它所维护的JSX组件树。这些树节点根据它们的类型被发送给对应的ViewManager(比如 节点会被发送给处理View实例的管理器)。元素的视图管理器创建出React组件代表的原生组件实例。如果该组件拥有属性,这些属性将由ViewManager 的函数接收,用来创建符合这些属性的原生组件实例。当属性发生变化后会进行同样的过程。

现在假设我们创建一个自定义的原生组件TestView:

class TestView extends View {
    ...
}

React Native端,它将显示成<TestView/>元素。

接下来我们就需要给TestViewh创建一个ViewManager

/**
 * 控制TestView的TestViewManager类
 */
class TestViewManager extends SimpleViewManager<TestView>{
    @Override
    public String getName();

    @Override
    protected TestView createViewInstance (ThemedReactContext context);

    @ReactProp(name = "someProp")
    public void setSomeProp(TestView testViewInstance, @Nullable String value);
}

我们实现了两个继承自父类的方法getNamecreateViewInstance ,以及自定义方法setSomeProp

  • getName方法返回组件名称,在JavaScript层被引用。
  • createViewInstance 方法用于在JavaScript层挂载React组件时创建TestView的实例。
  • setSomeProp方法在React组件属性包含初始值或新值时被调用。参数为相应的实例和属性值。

总体上看控制流从JavaScript层进入原生层。React组件负责渲染JSX组件,JSX组件的属性以及层次结构通过桥接层传递给原生层。

相应的ViewManager取得这些数据后,如果不存在已有实例,就调用createViewInstance 方法创建一个。

每个原生元素都有一个标识符,创建React组件的时候需要提供对应的标识符。这便是React Native能够映射两个层次结构的奥秘所在。

如果JSX组件定义了某些属性,它将寻找对应的函数(带有@ReactProp@ReactPropGroup 注释),并以相应的视图实例和属性值作为参数调用该方法。根据JavaScript组件定义的配置和布局,屏幕上就会渲染出最终的原生组件。



示意图

创建自定义原生组件

在接下来的示例中将用MapBox的SDK开发地图组件。先来编写React组件。

class MapDemo extends React.Component {
    constructor(props) {
        // 记得传入props
        // 我们终究希望原生层知道最初的状态
        super(props);

        // 起始坐标经纬度均为0
        var center = {latitude: 0, longitude: 0};

        this.state = {
            // 地图中心
            mapCenter: {
                ...center
            },
            // 编辑后的坐标(避免在用户输入时更新地图)
            editCenter: {
                ...center
            }
        };
    }

    /**
     * 地图位置移动后更新输入框
     */
    onCenterChanged({latitude, longitude}) {
        var editCenter = {latitude, longitude};
        this.setState({editCenter});
    }

    /**
     * 提交输入框内容后更新地图
     */
    updateMapCoordinates() {
        var latitude = parseFloat(this.state.editCenter.latitude);
        var longitude = parseFloat(this.state.editCenter.longitude);
        var mapCenter = {latitude, longitude};
        this.setState({mapCenter});
    }

    /**
     * 用户输入时更新单个输入框的内容
     */
    updateEditField(plain, value) {
        var editCenter = {
            ...this.state.editCenter,
            [plain]: value
        };
        this.setState({editCenter});
    }

    render() {
        return (
            <View>
                <MapBoxView
                    zoom={10}
                    center={this.state.mapCenter}
                    onRegionChanged={event =>this.onCenterChanged(event.nativeEvent.src)}
                />
                <TextInput
                    value={this.state.editCenter.latitude}
                    onChangeText={value =>updateEditField('latitude', value)}
                />
                <TextInput
                    value={this.state.editCenter.longitude}
                    onChangeText={value =>updateEditField('longitude', value)}
                />
                <TouchableHighlight onPress={() =>updateMapCoordinates()}>
                    <Text>Update</Text>
                    </TouchableHighlight>
            </View>
        );
    }
}

Android

接下来编写视图管理器类:

class ReactMapBoxViewManager extends SimpleViewManager<MapView>{
    public static final String REACT_CLASS = "ReactMapBoxView";
    public static final String REACT_PROP_CENTER = "center";
    public static final String REACT_PROP_ZOOM = "zoom";
    private static final String EVENT_REGION_CHANGED = "onRegionChanged";
    private String accessToken;

    ReactMapBoxViewManager(String accessToken) {
        this.accessToken = accessToken;
    }

    /**
     * React组件的名称
     */
    @Override
    public String getName() {
        return REACT_CLASS;
    }

    /**
     * 创建新的MapView实例
     * 该过程在新的React组件MapBox挂载到应用的时候执行
     */
    protected MapView createViewInstance(ThemedReactContexted themedReactContext) {
        MapView mapView = new MapView(themedReactContext, accessToken);
        // 必需的调用
        mapView.onCreate(null);
        return mapView;
    }

    @ReactProp(name = REACT_PROP_CENTER)
    public void setCenter(MapView view, @Nullable ReadableMap center) {
        if (center != null) {
            double latitude = center.getDouble("latitude");
            double longitude = center.getDouble("longitude");
            CameraPosition cameraPosition = new CameraPosition.Builder()
                .target(new LatLng(latitude, longitude))
                .build();
            view.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
        }
    }

    @ReactProp(name = REACT_PROP_ZOOM)
    public void setZoom(MapView view, double zoomLevel) {
        view.setZoom(zoomLevel);
    }

    @Override
    @Nullable
    public Map getExportedCustomDirectEventTypeConstants() {
        MapBuilder.Builder builder = MapBuilder.builder();
        builder.put(EVENT_REGION_CHANGED, MapBuilder.of("registrationName",
        EVENT_REGION_CHANGED));
        return builder.build();
    }

    @ReactMethod(name = EVENT_REGION_CHANGED, defaultBoolean = true)
    public void onRegionChangedListener(final MapView map, Boolean value) {
        view.addOnMapChangedListener(
            new MapView.onMapChangedListener() {
            @Override
            public void onMapChanged(int change) {
                if (change == MapView.REGION_DID_CHANGE ||
                change == MapView.REGION_DID_CHANGE_ANIMATED) {
                    WritableMap event = Arguments.createMap();
                    WritableMap location = Arguments.createMap();

                    location.putDouble("latitude", view.getCenterCoordinate().getLatitude());
                    location.putDouble("longitude", view.getCenterCoordinate().getLongitude());
                    location.putDouble("zoom", view.getZoom());
                    event.putMap("src", location);

                    ReactContext reactContext = (ReactContext) view.getContext();
                    context.getJSModule(RCTEventEmitter.class)
                        .receiveEvent(view.getId(), EVENT_REGION_CHANGED, event);
                }
            }
        });
    }
}

这个React组件会在某种方式下被转译成原生层的代码。MapBoxView将映射到ReactMapBoxViewManager 上,它有一个以@ReactProp(name = "center") 进行注释的方法。根据这种映射关系,React内部就可以作出以下判断。
- (1) 调用哪个方法。
- (2) 该方法需要的参数类型是什么。

onRegionChangedListener是处理用户移动地图的回调函数,React Native需要一种策略把事件的传播给绑定在React组件属性的函数上,这就需要重写getExportedCustomDirectEventTypeConstants方法,该方法负责定义事件、组件,以及绑定了回调函数的组件属性之间的映射关系。

它接受事件名称以及一个映射值registrationName:callbackPropertyName作为参数。这样React Native在分发事件给组件时就知道要查找的属性名称。

注意: JavaScript层接收了来自原生层的事件后,原生事件对象位于JavaScript事件对象的nativeEvent属性中。

JavaScript

原生组件已经就绪,下面要在JavaScript层为它定义React部分的接口。此处将会定义允许的属性类型。基本来讲,React组件只需要定义组件接口的部分,不需要定义完整的类。

// mapBox.js

import {PropTypes} from 'react';
import {requireNativeComponent, View} from 'react-native';

// 定义暴露给原生组件的接口
var iface = {
    name: 'MapBoxView',
    propTypes: {
        center: PropTypes.shape({
            latitude: PropTypes.number,
            longitude: PropTypes.number
        }),
        zoom: PropTypes.number,
        onRegionChanged: PropTypes.func,
        ...View.propTypes
    }
};

// export语句导出的接口,会被原生组件导入成JSX组件来使用
export default requireNativeComponent('ReactMapBoxView', iface);

任何原生层所定义的属性都必须在接口中定义好它们的类型。如果有任何遗漏,会出现如下图所示的红色警告界面通知你。



错误提示

小结

总体来看,React Native组件就是原生组件,只不过它还提供了一个代理来实现属性的映射,以及负责与JavaScript层交互的媒介。

原生模块的学习就留到下一篇,敬请期待…

2017-10-02 15:27:30 chaunceyw 阅读数 3714
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57520 人正在学习 去看看 李宁

写在前面

  • 目前主流的应用大体分成三类:Native App, Web App, Hybrid App.

三大主流的应用
  • Native App特点:
    • 性能好
    • 完美的用户体验
    • 开发成本高,无法跨平台
    • 升级困难(审核),维护成本高
  • Web App特点:

    • 开发成本低,更新快,版本升级容易,自动升级
    • 跨平台,Write Once , Run Anywhere
    • 无法调用系统级的API
    • 临时入口,用户留存度低
    • 性能差,体验差,设计受限制
    • 相比Native App,Web App体验中受限于以上5个因素:网络环境,渲染性能,平台特性,受限于浏览器,系统限制。
  • Hybrid App特点:

    • Native App 和 Web App 折中的方案,保留了 Native App 和 Web App 的优点。
    • 但是还是性能差。页面渲染效率低,在Webview中绘制界面,实现动画,资源消耗都比较大,受限于技术,网速等因素

Snip20161028_3.png
  • 为了解决上述问题,一套高效率,高性能的跨平台方案成为了大家热衷的话题,也就有了下面要比较的weex和react native.

基本概念


weex

  • 简介:
    weex是阿里巴巴公司与2016年6月开源的一种用于构建移动跨平台的UI框架
  • 特点:
    • 1.Lightweight:轻量级,语法简单,易于使用
    • 2.Extendable:可扩展,丰富内置组件,可扩展的API,
    • 3.High Performance:高性能
    • 核心理念:
    • Write Once Run Everywhere
    • 基于JS开发框架:
    • weex基于vue.js

React Native

  • 简介:
    • Facebook在2015年3月在F8开发者大会上开源的跨平台UI框架
  • 核心理念:LEARN ONCE, WRITE ANYWHERE
  • 基于JS开发框架:
    • React Native基于React

知识拓展:vue.js和React

Vue:

  • 是一个构建数据驱动的 web 界面的库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件.

React:

  • 基于HTML的前端界面开发正变得越来越复杂,其本质问题基本都可以归结于如何将来自于服务器端或者用户输入的动态数据高效的反映到复杂的用户界面上。而来自Facebook的React框架正是完全面向此问题的一个解决方案,按官网描述,其出发点为:用于开发数据不断变化的大型应用程序。相比传统型的前端开发,React开辟了一个相当另类的途径,实现了前端界面的高效率高性能开发。

Vue.js和React的异同:

Vue和React的区别

Weex和React Native的异同


相同点:

  • 都采用Web的开发模式,使用JS开发;
  • 都可以直接在Chrome中调试JS代码;
  • 都支持跨平台的开发;
  • 都可以实现hot reload,边更新代码边查看效果;

不同点:

JS引擎

什么是JS引擎

学习成本

  • 1.环境配置:
    • ReactNative需要按照文档安装配置很多依赖的工具,相对比较麻烦。 weex安装cli之后就可以使用
  • 2.vue vs react:上面已经做过对比
    • react模板JSX学习使用有一定的成本 vue更接近常用的web开发方式,模板就是普通的html,数据绑定使用mustache风格,样式直接使用css

社区支持

  • Weex开源较晚,互联网上相关资料还比较少,社区规模较小;
  • React Native社区则比较活跃,可以参考的项目和资料也比较丰富

一张图:从渲染时间,内存使用,CPU占用,帧率(图形处理器每秒钟能够刷新几次,高的帧率可以得到更流畅、更逼真的动画。每秒钟帧数 (fps) 愈多,所显示的动作就会愈流畅。)


各种类型应用对比


链接:http://www.jianshu.com/p/cb6de4b85ea3
2016-11-27 20:23:20 xiangzhihong8 阅读数 6089
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57520 人正在学习 去看看 李宁

RN中文网关于原生模块(Android)的介绍可以看到,RN前端与原生模块之

间通信,主要有三种方法:

1)使用回调函数Callback,它提供了一个函数来把返回值传回给JavaScript

2)使用Promise来实现。

3)原生模块向JavaScript发送事件。

关于使用回调,这是最简单的一种通信,这里可以看看官网的实现,今天要讲的是滴三种由原生模块向JavaScript发送事件。

(1)首先,你需要定义一个发送事件的方法。如下所示:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /*原生模块可以在没有被调用的情况下往JavaScript发送事件通知。 
  2.     最简单的办法就是通过RCTDeviceEventEmitter, 
  3.     这可以通过ReactContext来获得对应的引用,像这样:*/  
  4.     public static void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap paramss)  
  5.     {  
  6.         System.out.println("reactContext="+reactContext);  
  7.   
  8.         reactContext  
  9.                 .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)  
  10.                 .emit(eventName, paramss);  
  11.   
  12.     }  

 

其中方法名可以任意,但是参数不可改变。该方法可以放在你要复用的原生类中(即为原生类1)。

需要注意的是,由于版本问题,该函数中的参数reactContext有可能为null,此时会报NullPointException的错误。所以我们需要手动给reactContext赋值,见步骤2.

(2)我们在原生类1中,定义变量public static ReactContext  MyContext;

然后在我们自定义的继承至ReactContextBaseJavaModule的类中给reactContext赋值。

 

如下所示:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class MyModule extends ReactContextBaseJavaModule {  
  2.   
  3.     private BluetoothAdapter mBluetoothAdapter = null;  
  4.     public MyModule(ReactApplicationContext reactContext) {  
  5.         super(reactContext);  
  6.   
  7.         原生类1.MyContext=reactContext;  
  8.   
  9.           
  10.     }  
  11. .......以下写被@ReactNative所标注的方法  
  12. ............................  
  13. ...................  
  14. }  

 

此时,reactContext将不会是null。也就不会报错。

 

(3)在某个原生函数中向JavaScript发送事件。如下所示:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. WritableMap event = Arguments.createMap();  
  2. sendEvent(MyContext, "EventName",event);  


(4)在RN前端监听事件。首先导入DeviceEventEmitter,即import{ DeviceEventEmitter } from 'react-native'

然后使用componentWillMount建立监听。

 

代码如下:

[javascript] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. componentWillMount(){    
  2.     
  3.                     DeviceEventEmitter.addListener('EventName'function() {    
  4.                            
  5.                          alert("send success");    
  6.                        });   
  7.   
  8.                                
  9. }  

 

注意:该监听必须放在class里边,和render、const对齐。

 

下边展示一个完整Demo,Demo功能如下:

 

(1)JavaScript端在监听一个事件。

(2)点击前端某行文字,调用原生方法。

(3)在原生方法中,延迟3s后向前端发送对应事件。

(4)前端接收到事件后,给出alert提示。

 

代码如下:

ManiActivity.Java

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.ywq;  
  2.   
  3. import com.facebook.react.ReactActivity;  
  4.   
  5. public class MainActivity extends ReactActivity {  
  6.   
  7.     /** 
  8.      * Returns the name of the main component registered from JavaScript. 
  9.      * This is used to schedule rendering of the component. 
  10.      */  
  11.     @Override  
  12.     protected String getMainComponentName() {  
  13.         return "ywq";  
  14.     }  
  15. }  


ManiApplication.java

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.ywq;  
  2.   
  3. import android.app.Application;  
  4. import android.util.Log;  
  5.   
  6. import com.facebook.react.ReactApplication;  
  7. import com.facebook.react.ReactInstanceManager;  
  8. import com.facebook.react.ReactNativeHost;  
  9. import com.facebook.react.ReactPackage;  
  10. import com.facebook.react.shell.MainReactPackage;  
  11.   
  12. import java.util.Arrays;  
  13. import java.util.List;  
  14.   
  15. public class MainApplication extends Application implements ReactApplication {  
  16.   
  17.   private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {  
  18.     @Override  
  19.     protected boolean getUseDeveloperSupport() {  
  20.       return BuildConfig.DEBUG;  
  21.     }  
  22.   
  23.     @Override  
  24.     protected List<ReactPackage> getPackages() {  
  25.       return Arrays.<ReactPackage>asList(  
  26.           new MainReactPackage(),  
  27.               new MyPackage()  
  28.       );  
  29.     }  
  30.   };  
  31.   
  32.   @Override  
  33.   public ReactNativeHost getReactNativeHost() {  
  34.       return mReactNativeHost;  
  35.   }  
  36. }  


MyModule.java

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.ywq;  
  2.   
  3. import com.facebook.react.bridge.ReactApplicationContext;  
  4. import com.facebook.react.bridge.ReactContextBaseJavaModule;  
  5. import com.facebook.react.bridge.ReactMethod;  
  6.   
  7. /** 
  8.  * Created by Administrator on 2016/10/30. 
  9.  */  
  10.   
  11. public class MyModule extends ReactContextBaseJavaModule {  
  12.   
  13.     public MyModule(ReactApplicationContext reactContext) {  
  14.   
  15.         super(reactContext);  
  16.   
  17.         //给上下文对象赋值  
  18.         Test.myContext=reactContext;  
  19.     }  
  20.   
  21.     @Override  
  22.     public String getName() {  
  23.   
  24.         return "MyModule";  
  25.     }  
  26.   
  27.   
  28.     @ReactMethod  
  29.     public void  NativeMethod()  
  30.     {  
  31.         //调用Test类中的原生方法。  
  32.         new Test().fun();  
  33.     }  
  34. }  


MyPackage.java

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.ywq;  
  2.   
  3. import com.facebook.react.ReactPackage;  
  4. import com.facebook.react.bridge.JavaScriptModule;  
  5. import com.facebook.react.bridge.NativeModule;  
  6. import com.facebook.react.bridge.ReactApplicationContext;  
  7. import com.facebook.react.uimanager.ViewManager;  
  8.   
  9. import java.util.ArrayList;  
  10. import java.util.Collections;  
  11. import java.util.List;  
  12.   
  13. /** 
  14.  * Created by Administrator on 2016/10/30. 
  15.  */  
  16.   
  17. public class MyPackage implements ReactPackage {  
  18.     @Override  
  19.     public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {  
  20.   
  21.         List<NativeModule> modules=new ArrayList<>();  
  22.         modules.add(new MyModule(reactContext));  
  23.   
  24.         return modules;  
  25.     }  
  26.   
  27.     @Override  
  28.     public List<Class<? extends JavaScriptModule>> createJSModules() {  
  29.         return Collections.emptyList();  
  30.     }  
  31.   
  32.     @Override  
  33.     public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {  
  34.         return Collections.emptyList();  
  35.     }  
  36. }  


 

Test.java

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.ywq;  
  2.   
  3. import android.provider.Settings;  
  4. import android.support.annotation.Nullable;  
  5.   
  6. import com.facebook.react.bridge.Arguments;  
  7. import com.facebook.react.bridge.ReactContext;  
  8. import com.facebook.react.bridge.WritableMap;  
  9. import com.facebook.react.modules.core.DeviceEventManagerModule;  
  10.   
  11. /** 
  12.  * Created by Administrator on 2016/10/30. 
  13.  */  
  14.   
  15. public class Test {  
  16.   
  17.      //定义上下文对象  
  18.     public static ReactContext myContext;  
  19.   
  20.     //定义发送事件的函数  
  21.     public  void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params)  
  22.     {  
  23.         System.out.println("reactContext="+reactContext);  
  24.   
  25.         reactContext  
  26.                 .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)  
  27.                 .emit(eventName,params);  
  28.     }  
  29.   
  30.     public  void fun()  
  31.     {  
  32.         //在该方法中开启线程,并且延迟3秒,然后向JavaScript端发送事件。  
  33.         new Thread(new Runnable() {  
  34.             @Override  
  35.             public void run() {  
  36.   
  37.                 try {  
  38.                     Thread.sleep(3000);  
  39.                 } catch (InterruptedException e) {  
  40.                     e.printStackTrace();  
  41.                 }  
  42.   
  43.                //发送事件,事件名为EventName  
  44.                 WritableMap et= Arguments.createMap();  
  45.                 sendEvent(myContext,"EventName",et);  
  46.   
  47.   
  48.             }  
  49.         }).start();  
  50.   
  51.     }  
  52.   
  53.   
  54. }  


 

前端index.android.js代码如下:

[javascript] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Sample React Native App 
  3.  * https://github.com/facebook/react-native 
  4.  * @flow 
  5.  */  
  6.   
  7. import React, { Component } from 'react';  
  8. import {  
  9.  AppRegistry,  
  10.   StyleSheet,  
  11.   Text,  
  12.   DeviceEventEmitter,  
  13.   NativeModules,  
  14.   View  
  15. } from 'react-native';  
  16.   
  17. export default class ywq extends Component {  
  18.   
  19.     componentWillMount(){    
  20.                        //监听事件名为EventName的事件  
  21.                     DeviceEventEmitter.addListener('EventName'function() {    
  22.                            
  23.                          
  24.   
  25.                          alert("send success");    
  26.   
  27.                        });   
  28.   
  29.                                  
  30. }  
  31.   
  32.   constructor(props) {  
  33.     super(props);  
  34.     this.state = {  
  35.         content: '这个是预定的接受信息',  
  36.     }  
  37. }  
  38.   
  39.   render() {  
  40.     return (  
  41.       <View style={styles.container}>  
  42.   
  43.         <Text style={styles.welcome}  
  44.          onPress={this.callNative.bind(this)}  
  45.         >  
  46.           当你点我的时候会调用原生方法,原生方法延迟3s后会向前端发送事件。  
  47.           前端一直在监听该事件,如果收到,则给出alert提示!  
  48.         </Text>  
  49.           
  50.         <Text style={styles.welcome} >  
  51.         {this.state.content}  
  52.          </Text>  
  53.   
  54.   
  55.       </View>  
  56.     );  
  57.   }  
  58.   
  59.   callNative()  
  60.   {  
  61.     NativeModules.MyModule.NativeMethod();  
  62.   }  
  63.    
  64.  }  
  65.   
  66. const styles = StyleSheet.create({  
  67.   container: {  
  68.     flex: 1,  
  69.     justifyContent: 'center',  
  70.     alignItems: 'center',  
  71.     backgroundColor: '#F5FCFF',  
  72.   },  
  73.   welcome: {  
  74.     fontSize: 20,  
  75.     textAlign: 'center',  
  76.     margin: 10,  
  77.   },  
  78.   instructions: {  
  79.     textAlign: 'center',  
  80.     color: '#333333',  
  81.     marginBottom: 5,  
  82.   },  
  83. });  
  84.   
  85. AppRegistry.registerComponent('ywq', () => ywq);  


运行结果如下所示:

点击之前:


 

调用原生方法并且等待3s后:


 

 

 

再说一个值得注意的地方,一般我们在接收到原生模块主动发来的事件时,都会进行一些操作,如更新UI,而不仅仅是弹出alert 。

例如我们需要更新UI,代码如下:

[javascript] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Sample React Native App 
  3.  * https://github.com/facebook/react-native 
  4.  * @flow 
  5.  */  
  6.   
  7. import React, { Component } from 'react';  
  8. import {  
  9.  AppRegistry,  
  10.   StyleSheet,  
  11.   Text,  
  12.   DeviceEventEmitter,  
  13.   NativeModules,  
  14.   View  
  15. } from 'react-native';  
  16.   
  17. export default class ywq extends Component {  
  18.   
  19.     componentWillMount(){    
  20.                       //监听事件名为EventName的事件  
  21.                     DeviceEventEmitter.addListener('EventName'function() {    
  22.                            
  23.                          this.showState();  
  24.   
  25.                          alert("send success");    
  26.   
  27.                        });   
  28.   
  29.                                  
  30. }  
  31.   
  32.   constructor(props) {  
  33.     super(props);  
  34.     this.state = {  
  35.         content: '这个是预定的接受信息',  
  36.     }  
  37. }  
  38.   
  39.   render() {  
  40.     return (  
  41.       <View style={styles.container}>  
  42.   
  43.         <Text style={styles.welcome}  
  44.          onPress={this.callNative.bind(this)}  
  45.         >  
  46.           当你点我的时候会调用原生方法,原生方法延迟3s后会向前端发送事件。  
  47.           前端一直在监听该事件,如果收到,则给出alert提示!  
  48.         </Text>  
  49.           
  50.         <Text style={styles.welcome} >  
  51.         {this.state.content}  
  52.          </Text>  
  53.   
  54.   
  55.       </View>  
  56.     );  
  57.   }  
  58.   
  59.   callNative()  
  60.   {  
  61.     NativeModules.MyModule.NativeMethod();  
  62.   }  
  63.    
  64.   showState()  
  65.   {  
  66.        this.setState({content:'已经收到了原生模块发送来的事件'})  
  67.   }  
  68. }  
  69.   
  70. const styles = StyleSheet.create({  
  71.   container: {  
  72.     flex: 1,  
  73.     justifyContent: 'center',  
  74.     alignItems: 'center',  
  75.     backgroundColor: '#F5FCFF',  
  76.   },  
  77.   welcome: {  
  78.     fontSize: 20,  
  79.     textAlign: 'center',  
  80.     margin: 10,  
  81.   },  
  82.   instructions: {  
  83.     textAlign: 'center',  
  84.     color: '#333333',  
  85.     marginBottom: 5,  
  86.   },  
  87. });  
  88.   
  89. AppRegistry.registerComponent('ywq', () => ywq);  

很明显:当收到事件时,改变一个文本框的内容,即更新UI。

运行结果如下,说明在此function中不能使用this,也就是我们并不能更新UI。


那我们能做到在接收到事件后更新UI等后续操作吗?

使用胖箭头函数(Fat arrow functions)

修改UI代码如下:

[javascript] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Sample React Native App 
  3.  * https://github.com/facebook/react-native 
  4.  * @flow 
  5.  */  
  6.   
  7. import React, { Component } from 'react';  
  8. import {  
  9.  AppRegistry,  
  10.   StyleSheet,  
  11.   Text,  
  12.   DeviceEventEmitter,  
  13.   NativeModules,  
  14.   View  
  15. } from 'react-native';  
  16.   
  17. export default class ywq extends Component {  
  18.   
  19.     componentWillMount(){    
  20.                       //监听事件名为EventName的事件  
  21.                      
  22.                     DeviceEventEmitter.addListener('EventName', ()=> {    
  23.                            
  24.                          this.showState();  
  25.                          alert("send success");    
  26.   
  27.                        });   
  28.                   
  29. }  
  30.   
  31.   constructor(props) {  
  32.     super(props);  
  33.     this.state = {  
  34.         content: '这个是预定的接受信息',  
  35.     }  
  36. }  
  37.   
  38.   render() {  
  39.     return (  
  40.       <View style={styles.container}>  
  41.   
  42.         <Text style={styles.welcome}  
  43.          onPress={this.callNative.bind(this)}  
  44.         >  
  45.           当你点我的时候会调用原生方法,原生方法延迟3s后会向前端发送事件。  
  46.           前端一直在监听该事件,如果收到,则给出alert提示!  
  47.         </Text>  
  48.           
  49.         <Text style={styles.welcome} >  
  50.         {this.state.content}  
  51.          </Text>  
  52.   
  53.   
  54.       </View>  
  55.     );  
  56.   }  
  57.   
  58.   callNative()  
  59.   {  
  60.     NativeModules.MyModule.NativeMethod();  
  61.   }  
  62.    
  63.   showState()  
  64.   {  
  65.        this.setState({content:'已经收到了原生模块发送来的事件'})  
  66.   }  
  67. }  
  68.   
  69. const styles = StyleSheet.create({  
  70.   container: {  
  71.     flex: 1,  
  72.     justifyContent: 'center',  
  73.     alignItems: 'center',  
  74.     backgroundColor: '#F5FCFF',  
  75.   },  
  76.   welcome: {  
  77.     fontSize: 20,  
  78.     textAlign: 'center',  
  79.     margin: 10,  
  80.   },  
  81.   instructions: {  
  82.     textAlign: 'center',  
  83.     color: '#333333',  
  84.     marginBottom: 5,  
  85.   },  
  86. });  
  87.   
  88. AppRegistry.registerComponent('ywq', () => ywq);  
运行之后,界面刷新了。



2016-06-12 17:04:28 Miss_YD 阅读数 2712
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57520 人正在学习 去看看 李宁

React Native 植入原生应用

  1. React Native 植入原生应用 官方文档中文版要是按照文档没有错误的话,就不用看下面的了。。。
  2. 新建本地iOS Project(有的话就不用了)(跟文档一样
  3. 新建Podfile文件,在.xcodeproj文件的路径下,引入React需要的依赖文件然后再写点东西(跟文档一样)

    • path 这个路径是你的js的文件夹的路径,主要是为了找到node_modules/react-native这个文件夹目录,按照自己的实际project来
    pod 'React', :path => ‘../RN/node_modules/react-native', :subspecs => [
    'Core',
    'RCTImage',
    'RCTNetwork',
    'RCTText',
    'RCTWebSocket',
    ]
    
  4. 新建一个package.json文件(如果js和本地project是分开的两个文件夹,把package.json放在js那个文件夹里,不是分开的就放在根目录就行)(我按照文档植入的时候,会有提示说找不到package.json文件,所以就新建了一个)

    {
      "name": <YOUR_PROJECT_NAME>,
      "version": "0.0.1",
      "private": true,
      "scripts": {
        "start": "node ../RN/node_modules/react-native/local-cli/cli.js start" # <自己的node_modules/react-native/local-cli/cli.js 地址>
      },
      "dependencies": {
        "react": "^15.1.0", # 换成自己的版本号
        "react-native": "^0.27.2" # 换成自己的版本号
      }
    }
    
  5. 在package.json文件的目录下,执行npm install react-native --save生成node_modules文件夹,如果提示react-native@0.27.2 requires a peer of react@15.1.0 but none was installed.,看一下文件夹的目录,有没有react这个文件夹,没有的话再执行npm install react --save,有的话就不需要了

  6. 新建index.ios.js文件,写点东西(跟文档一样)

  7. 在js文件夹的目录下执行cd node_modules/react-native/进入该文件夹

  8. 执行npm run start -- --root <package.json文件所在的路径>

  9. Run Project ,OK 了。。


2018-07-09 15:57:02 r122555 阅读数 161
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57520 人正在学习 去看看 李宁

那么书接上回,今天主要是继续探究React Native与原生模块的架构方式。

原生模块

原生模块可以访问activity、运行环境、GPS、存储空间等。原生模块就是能够在JavaScript层调用的API。

因为对原生模块的全部请求都要异步执行。如果原生方法需要为JavaScript层的调用返回数据,该操作将通过promise或者回调函数来完成。React Native为这两种方式都提供了接口。

剖析原生模块

IOSAndroid对原生模块的实现思路是这样的:

  1. 每个模块继承自原生模块父类。
  2. 每个模块都定义了名称,以便JavaScript层访问。
  3. 每个模块都导出可调用的方法,并包含Java的注释或Objective-C的宏。

01. Android

Android平台的原生模块继承ReactContextBaseJavaModule ,并且必须实现getName 方法,它的返回值作为JavaScript模块的名称。

任何允许JavaScript层调用的方法,都带有@ReactMethod 注释。React的内部机制会把JavaScript层的请求映射到这些方法上。每个方法的身份由它的签名、名称以及参数进行鉴别。方法所需的任何参数都会被转换成对应的属性类型。

简单的示例在上一篇已经提及,请参阅React Native与原生模块、组件之间的关系浅析

02. JavaScript

JavaScript层把原生模块作为NativeModules 对象的一个属性,并且任何带有@ReactMethod 注释或者属于RCT_EXPORT_METHOD宏的方法都能够被调用。

参数

原生模块的方法和普通方法一样可以接受参数。

针对于Android,带有@ReactMethod注释的方法支持以下参数类型。

  • Boolean -> Bool
  • Integar -> Number
  • Double -> Number
  • Float -> Number
  • String -> String
  • Callback -> function
  • ReadableMap -> Object
  • ReadableArray -> Array

回调函数和promise

由于所有的通信过程都是异步的,原生模块不能有返回值。React Native使用回调函数和promise类作为解决方案。使用方式与在JavaScript代码中执行回调函数以及promiseresolve/reject 操作完全一样。

01 回调函数

原生回调函数接口遵循两个参数的约定:
- 第一参数表示错误对象(没有错误的情况则为null)。
- 第二参数用来提供要响应的数据。

示例:

@ReactMethod
public void add (int a, int b, Callback callback) {
  int sum = a + b;
  callback.invoke(null, sum);
}

02 promise

promise可以在操作成功后把值返回给JavaScript层。响应要么成功要么失败。JavaScript层的接口保留了我们所熟悉的.then.catch方法。

示例:

@ReactMethod
public void failIfFalse (boolean value, Promise promise) {
  if (value) {
    promise.resolve("Your value was true, so it resolved.");
  } else {
    promise.reject(new Error("Your value was false, so it rejected"));
  }
}

03 返回请求成功的对象

我们已经了解到,可以通过回调函数和promise在请求成功后把标量值返回给JavaScript层。大多数情况下响应内容应该是JSON对象的形式。

Android提供了WritableMap 来返回JSON响应。该接口和包含特定类型put方法的Map很相似。

  • pushNull
  • putBoolean
  • putDouble
  • putInt
  • putString
  • putArray
  • putMap

String类型的键名作为每个方法的第一参数。
第二参数是方法名相关类型的值。

示例:

@ReactMethod
public void jsonResponse(Promise promise) {
  WritableMap jsonMap = new WritableNativeMap();
  jsonMap.putString("stringValue", "A string");
  jsonMap.putBoolean("booleanValue", true);
  jsonMap.putInt("intValue", 123);
  promise.resolve(jsonMap);
}

常量

原生模块也可以导出常量,供JavaScript访问。

要使原生模块的常量可用,需要实现父类的getConstants (Android)或constantsToExport (iOS)方法,这两个方法将返回键值对的映射,键名会作为JavaScript模块的属性而存在。

示例:

@Override
public Map<String, Object> getConstants() {
  final Map<String, Object> constants = new HashMap<>();
  constants.put("HELLO", "world");
  constants.put("FOO", "bar");
}

事件

DeviceEventEmitter模块用于把事件从原生层传到JavaScript层。

针对Android而言,ReactContext暴露出getJSModule 方法用于获取其他模块,为它传入类参数后返回相应的实例。它将用来获取RCTDeviceEventEmitter模块并调用emit 方法,接着事件就会通过桥接层发送到JavaScript层,DeviceEventEmitter模块的监听器就会捕获这个事件,并通知其他任意的事件监听器。

示例:

@ReactMethod
public void sendTestEvent() {
  String eventName = "customEvent";
  WritableMap params = new WritableNativeMap();
  params.putString("param", "Hello world");
 
  getReactApplicationContext()
    .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
    .emit(eventName, params);
}

JavaScript上面的调用方式如下:

import {DeviceEventEmitter, NativeModules} from 'react-native';
const {ExampleModule} = NativeModules;
DeviceEventEmitter.addListener('customEvent', e => {
  console.log('received event', e.nativeEvent.param); // 2
});
 
ExampleModules.sendTestEvent(); // 1

总结

通过本篇的学习,了解了原生模块和组件的工作原理。知道了react native与原生层的调用关系。原生模块就是JavaScript层可以调用的一些方法的集合。

没有更多推荐了,返回首页