React Native原生模块向JS传递数据的几种方式(Android)

erjnae9012 8年前
   <p>在做React Native开发的时候避免不了的需要原生模块和JS之间进行数据传递,这篇文章将向大家分享原生模块向JS传递数据的几种方式。</p>    <h2><strong>方式一:通过Callbacks的方式</strong></h2>    <p>说起Callbacks大家都不陌生,它是最常用的设计模式之一。无论是Java,Object-c,C#,还是JavaScript等都会看到Callbacks的身影。</p>    <p>原生模块支持Callbacks类型的参数,该Callbacks对应JS中的function。</p>    <p>在原生模块中:</p>    <pre>  <code class="language-javascript">public class RNTestModule extends ReactContextBaseJavaModule{      public RNTestModule(ReactApplicationContext reactContext) {          super(reactContext);      }      @Override      public String getName() {          return "RNTest";      }      @ReactMethod    public void measureLayout(        int tag,        int ancestorTag,        Callback errorCallback,        Callback successCallback) {      try {        measureLayout(tag, ancestorTag, mMeasureBuffer);        map.putDouble("relativeX",1);        map.putDouble("relativeY", 1);        map.putDouble("width", 2);        map.putDouble("height",3);        successCallback.invoke(relativeX, relativeY, width, height);      } catch (IllegalViewOperationException e) {        errorCallback.invoke(e.getMessage());            }  }</code></pre>    <p>在上述代码中, measureLayout 方法的参数中后两个就是Callbacks,当原生模块处理成功时通过successCallback回调来告知JS处理成功的结果,当原生模块发生异常时,则通过errorCallback回调来JS处理异常。</p>    <p>在JS模块中:</p>    <pre>  <code class="language-javascript">RNTest.measureLayout(    100,    100,    (msg) => {      console.log(msg);    },    (x, y, width, height) => {      console.log(x + ':' + y + ':' + width + ':' + height);    }  );</code></pre>    <p>上述代码,是在JS模块中调用原生模块的方法 measureLayout ,同时向它传递了四个参数,后两个是function类型的参数用于接收原生模块的处理结果。</p>    <p>通过上述的方式,JS调用原生模块的 measureLayout 方法,原生模块则通过 errorCallback 与 successCallback Callbacks来将处理结果传递到JS。</p>    <p>这种“You call me,I will callback”,的方式小伙伴们都看懂了吧。</p>    <h2><strong>方式二:通过Promises的方式</strong></h2>    <p>Promises是ES6的一个新的特性,在React Native中你会看到Promises的大量使用。</p>    <p>原生模块也是支持Promises的,这对喜欢使用Promises的小伙伴则是一个很好的消息。</p>    <p>在原生模块中:</p>    <pre>  <code class="language-javascript">public class RNTestModule extends ReactContextBaseJavaModule{      public RNTestModule(ReactApplicationContext reactContext) {          super(reactContext);      }      @Override      public String getName() {          return "RNTest";      }      @ReactMethod      public void measureLayout(              int tag,              int ancestorTag,              Promise promise) {          try {              WritableMap map = Arguments.createMap();              map.putDouble("relativeX",1);              map.putDouble("relativeY", 1);              map.putDouble("width", 2);              map.putDouble("height",3);                promise.resolve(map);          } catch (IllegalViewOperationException e) {              promise.reject(e);          }      }  }</code></pre>    <p>上述代码中, measureLayout 方法接收的最后一个为Promise,当相应的处理结果出来之后原生模块通过调用Promise的相应方法来向JS模块传递处理成功,或处理失败的数据。</p>    <p>提示:在原生模块中Promise类型的参数要放在最后一位,这样JS调用的时候才能返回一个Promise。</p>    <p>在JS模块中:</p>    <pre>  <code class="language-javascript">async test() {    try {      var {          relativeX,          relativeY,          width,          height,      } = await RNTest.measureLayout(100, 100);        console.log(relativeX + ':' + relativeY + ':' + width + ':' + height);      } catch (e) {      console.error(e);    }  }</code></pre>    <p>在上述代码中,通过ES7的新特性async/await来修饰了 test 方法,来以同步方式调用原生模块的 measureLayout 方法,如果原生模块处理成功, 那么JS中relativeX,relativeY,width,height会获得相应的值,如果原生模块处理失败,则会抛出异常。</p>    <p>如果,不希望以同步的形式调用,可以这样写:</p>    <pre>  <code class="language-javascript">test2(){    RNTest.measureLayout(100,100).then(e=>{      console.log(e.relativeX + ':' + e.relativeY + ':' + e.width + ':' + e.height);      this.setState({        relativeX:e.relativeX,        relativeY:e.relativeY,        width:e.width,        height:e.height,      })    }).catch(error=>{      console.log(error);    });  }</code></pre>    <p>以上就是通过Promises的方式向JS传递数据的方式,小伙伴们看懂了吗。</p>    <p>上述两种方式,与,都可以向JS模块传递数据,但都是只能传递一次。 如果,你需要多次向JS模块传递数据(如:按键事件)上述方式还是不够好,下面就像大家分享可以多次传递数据的方式。</p>    <h2><strong>方式三:通过发送事件的方式</strong></h2>    <p>原生模块支持另外一种向JS模块传递数据的方式,通过发送事件的方式。</p>    <p>原生模块,可以向JS传递事件而不需要直接的调用,就像Android中的广播,iOS中的通知中心。</p>    <p>下面就向大家演示通过 RCTDeviceEventEmitter ,来向JS传递事件。</p>    <p>在原生模块中:</p>    <pre>  <code class="language-javascript">@Override  public void onHandleResult(String barcodeData) {      WritableMap params = Arguments.createMap();      params.putString("result", barcodeData);      sendEvent(getReactApplicationContext(), "onScanningResult", params);  }    private void sendEvent(ReactContext reactContext,String eventName, @Nullable WritableMap params) {      reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)              .emit(eventName, params);  }</code></pre>    <p>上述代码向JS模块发送了一个名为“onScanningResult”的事件,并携带了“params”作为参数。</p>    <p>在JS模块中:</p>    <p>下面是在JS代码中进行监听原生模块发出的名为“onScanningResult”的事件。</p>    <pre>  <code class="language-javascript">componentDidMount() {      //注册扫描监听      DeviceEventEmitter.addListener('onScanningResult',this.onScanningResult);  }  onScanningResult = (e)=> {      this.setState({          scanningResult: e.result,      });      // DeviceEventEmitter.removeListener('onScanningResult',this.onScanningResult);//移除扫描监听  }</code></pre>    <p>在JS中通过 DeviceEventEmitter 注册监听了名为“onScanningResult”的事件,当原生模块发出名为“onScanningResult”的事件后,绑定在该事件上的 onScanningResult = (e) 会被回调。 然后通过 e.result 就可获得事件所携带的数据。</p>    <p>心得:如果在JS中有多处注册了 onScanningResult 事件,那么当原生模块发出事件后,这几个地方会同时收到该事件。不过大家也可以通过 DeviceEventEmitter.removeListener('onScanningResult',this.onScanningResult) 来移除对名为“onScanningResult”事件的监听。</p>    <p>另外,JS模块也支持通过 Subscribable mixin,也注册监听事件,因为ES6已经不再推荐使用mixin,所以在这里也就不向大家介绍了。</p>    <h2><strong>三种方式的优缺点</strong></h2>    <table>     <thead>      <tr>       <th>方式</th>       <th>缺点</th>       <th>优点</th>      </tr>     </thead>     <tbody>      <tr>       <td>通过Callbacks的方式</td>       <td>只能传递一次</td>       <td>传递可控,JS模块调用一次,原生模块传递一次</td>      </tr>      <tr>       <td>通过Promises的方式</td>       <td>只能传递一次</td>       <td>传递可控,JS模块调用一次,原生模块传递一次</td>      </tr>      <tr>       <td>通过发送事件的方式</td>       <td>原生模块主动传递,JS模块被动接收</td>       <td>可多次传递</td>      </tr>     </tbody>    </table>    <p> </p>    <p> </p>    <p>来自:http://www.cboy.me/2016/09/29/React-Native原生模块向JS传递数据的几种方式/</p>    <p> </p>