React Native + Cordova WebView 演进:Plugin 篇

JulGavin 5年前
   <p>最近,项目上正在打算使用 React Native 来重写/重构/演讲原来的应用。由于早先使用 Cordova + Ionic 的时候,项目的业务代码很长一段时间里,主要是由我一个编写的。与此同时,也不会分配充足的人力,用于重写现有的业务逻辑。</p>    <p>因此,作为一个咨询师,我提供了几个不同的重构方案,并建议客户使用 React Native + WebView 的形式来进行演进。即在 React Native 里使用 WebView 来嵌入原有的业务逻辑,新的业务逻辑则采用 React Native 进行。考虑到未来的一段时间内, 业务代码将会继续采用 WebView 编写,而技术代码则用 React Native 编写,这是比较理想的方案。</p>    <p>而作为演进的其中一个难点是:重写原有的 Cordova 插件。我们使用到了数量众多的 Cordova 插件,如 Toast、日期控件等等。这个时候,就需要借助于 React Native WebView 的通信来做这件事。</p>    <h2>React Native WebView 通信</h2>    <p>早期的 React Native WebView 并不能直接与 WebView 通信,而自 React Native 0.37 版本后,则提供了:</p>    <ul>     <li>onMessage</li>     <li>postMessage</li>    </ul>    <p>两个方法来与 WebView 进行交互,如下是一个简单的 DEMO:</p>    <pre>  <code class="language-javascript">...    class xxWebView extends React.Component {      webview: WebView      handleMessage = (evt: any) => {      // doSomething()      }      render() {          return (              <WebView                  ref={webview => this.webview = webview}                  onMessage={this.handleMessage}              />          )      }  }</code></pre>    <p>在我们的 WebView 里,只需要执行下:</p>    <pre>  <code class="language-java">window.postMessage({ plugin: 'TOAST' })</code></pre>    <p>就可以向插件发送信息。因此,对于完成我们的插件来说,只需要做到下面的步骤:</p>    <ul>     <li>当需要调用原生插件的时候,在 WebView 里调用 window.postMessage 来传递,相应的 <strong>插件名 + 插件的参数</strong></li>     <li>React Native 通过 onMessage 来处理对应的类型,并调用对应的插件</li>     <li>当需要返回结果时,通过 webview.postMessage 来传递参数,并带上相应的 <strong>插件名 + 返回结果</strong></li>     <li>在 WebView 端 ,如果想获取返回的结果,则需要 window.document.addEventListener 来监听 message 事件</li>     <li>最后,再根据返回的值来做相应的处理。</li>    </ul>    <p>接着,让我们来看一个简单的日期控件的 DEMO。</p>    <h2>Cordova WebView 调用 React Native 日期控件</h2>    <h3>WebView</h3>    <p>重写这段逻辑前,先让我们来看看原有的逻辑代码:</p>    <pre>  <code class="language-java">function onSuccess(date) {  // 更新时间  }    datePicker.show(options, onSuccess, null);</code></pre>    <p>我们通过 options 来传递参数,而 onSuccess 则是成功的回调。不过,由于已经没有 Cordova 的机制,这里的 success 和 error 的回调就没有啥用了。</p>    <p>因此,在 WebView 上这段逻辑就变成了:</p>    <pre>  <code class="language-java">$rootScope.$on('Bridge.datePicker', function(event, data) {  // 更新时间  });    BridgeHelper.datePicker(options);    //BridgeHelper.js 中的相关代码    window.postMessage(JSON.stringify({    command: 'DATE_PICKER',    payload: options  }));</code></pre>    <p>同时,我们有一个全局的监听函数,在这里面判断是否有对应的 command 类型。如果是我们需要的 DATE_PICKER,并且是成功地修改值,便会发出这样的一个广播,上面的代码就可以成功地更新时间。</p>    <pre>  <code class="language-java">window.document.addEventListener('message', function (e) {    var data = JSON.parse(e.data);    if(data.command  && data.command === 'DATE_PICKER' && data.success) {      $rootScope.$broadcast('Bridge.datePicker', data)    }  });</code></pre>    <p>这个原理与之前提到的 <a href="/misc/goto?guid=4959749240335412648" rel="nofollow,noindex">Ionic 与 Cordova 插件编写:基于事件与广播的机制</a> 是相似的,通过全局事件来控制逻辑。</p>    <h3>React Native</h3>    <p>在 React Native 端,则也是对相应的 handleMessage 进行处理,然后调用相应的组件来处理,如下是调用系统的控件:</p>    <pre>  <code class="language-java">DatePickerHandler.showDatePicker = (payload, webView) => {    const showPicker = async (options, webView) => {      try {        const { command, year, month, day } = await DatePickerAndroid.open(options);        if (command === DatePickerAndroid.dismissedcommand) {      //        } else {          const date = new Date(year, month, day);          webView.postMessage(JSON.stringify({            command: 'DATE_PICKER',            success: true,            date,          }));        }      } catch ({ code, message }) {        console.warn('Cannot open date picker', message);      }    };      showPicker(options, webView);  };</code></pre>    <p>通过这样复杂的工作,我们就可以完成大部分的工作。</p>    <p> </p>    <p>来自:http://www.phodal.com/blog/react-native-inside-cordova-webview-with-plugin/</p>    <p> </p>