详解react-native WebView 返回处理(非回调方法可解决)


Posted in Javascript onFebruary 27, 2018

1.前言

项目中有些页面内容是变更比较频繁的,这些页面我们会考虑用 网页 来解决。

在RN项目中提供一个公用的Web页,如果是网页内容,就跳转到这个界面展示。

此时会有一个问题是,网页会有一级页面,二级页面,这就会设计到导航栏返回键的处理(以及在Android上返回键的处理)。

这个问题,在RN官网就可找到解决方式。就是用 onNavigationStateChange 这个回调方法记录当前的导航状态,从而判断是返回上一级页面还是退出这个网页,回到App的其他界面。

但是,当网页的实现是React时,就会有问题了,你会发现,当页面跳转的时候,onNavigationStateChange这个回调方法没有回调!!!怎么肥四!!

一开始尝试了把网页地址换成百度的,可以接收回调,一切都运行的很好,可是换成我们的链接就不行,所以就把锅甩给了后台,以为是React哪边写的不对。

因为上一个项目时间紧,没有时间好好去看一下源码,就想了一个不是很完善的解决方案,就是网页用js来回调App来告知现在的导航状态,这样的解决方式显示是不友好的。

现在稍微有点时间看了源码才知道真正原因。

2.原因

下面就分析一下这个问题的原因和我的解决方式。

1.首先,先找到源码的位置。

node_modules\react-native\ReactAndroid\src\main\java\com\facebook\react\views\webview

node_modules\react-native\Libraries\Components\WebView

目录结构是这样的:

详解react-native WebView 返回处理(非回调方法可解决) 

详解react-native WebView 返回处理(非回调方法可解决)

2.实现的代码段 (JAVA端)

RN的实际运行代码都是原生代码,所以,像WebView组件的一些事件回调,其实都是原生代码中的回调触发的。如下

(ReactWebViewManager.java) rn版本0.47.1

protected static class ReactWebViewClient extends WebViewClient { //WebViewClient就是我们在写Android原生代码时,监听网页加载情况使用的工具。
   protected static final String REACT_CLASS = "RCTWebView"; //定义的原生组件名,在后面JS中会对应到。

  //...

  @Override
  public void onPageStarted(WebView webView, String url, Bitmap favicon) { //有很多回调方法,此处只举一例
   super.onPageStarted(webView, url, favicon);
   mLastLoadFailed = false;

   dispatchEvent(
     webView,
     new TopLoadingStartEvent(   //自己定义的时间,dispatch后,事件会传给js
       webView.getId(),
       createWebViewEvent(webView, url)));
  }

  //...
 }

(ReactWebViewManager.java) rn版本0.43.3  ,RN不同版本会有代码调整,所以RN升级的时候,需要仔细的回归测试。

protected static class ReactWebViewClient extends WebViewClient { //WebViewClient就是我们在写Android原生代码时,监听网页加载情况使用的工具。
   protected static final String REACT_CLASS = "RCTWebView"; //定义的原生组件名,在后面JS中会对应到。

  //...

  @Override
  public void onPageStarted(WebView webView, String url, Bitmap favicon) { //有很多回调方法,此处只举一例
   super.onPageStarted(webView, url, favicon);
   mLastLoadFailed = false;

   dispatchEvent(
     webView,
     new TopLoadingStartEvent(   //自己定义的时间,dispatch后,事件会传给js
       webView.getId(),
       createWebViewEvent(webView, url)));
  }

  @Override
  public void doUpdateVisitedHistory(WebView webView, String url, boolean isReload) { //坑在这,这里就是导航有变化的时候会回调在这个版本是有这个处理的,但是不知道在哪个版本删掉了 -.-
   super.doUpdateVisitedHistory(webView, url, isReload);

   dispatchEvent(
     webView,
     new TopLoadingStartEvent(
       webView.getId(),
       createWebViewEvent(webView, url)));
  }

  //...
 }

(TopLoadingStartEvent.java) 回调JS的Event

public class TopLoadingStartEvent extends Event<TopLoadingStartEvent> {
 public static final String EVENT_NAME = "topLoadingStart";  //对应方法是onLoadingStart, 因为对RN的结构不熟悉,在此处花了很长时间研究是怎么对应的,最后找到了定义对应的文件
 private WritableMap mEventData;

 public TopLoadingStartEvent(int viewId, WritableMap eventData) {
  super(viewId);
  mEventData = eventData;
 }

 @Override
 public String getEventName() {
  return EVENT_NAME;
 }

 @Override
 public boolean canCoalesce() {
  return false;
 }

 @Override
 public short getCoalescingKey() {
  // All events for a given view can be coalesced.
  return 0;
 }

 @Override
 public void dispatch(RCTEventEmitter rctEventEmitter) {
  rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mEventData);
 }
}

(node_modules\react-native\ReactAndroid\src\main\java\com\facebook\react\uimanager\UIManagerModuleConstants.java)

这个文件里,定义了对应关系

/**
 * Constants exposed to JS from {@link UIManagerModule}.
 */
/* package */ class UIManagerModuleConstants {

 /* package */ static Map getDirectEventTypeConstants() {
  return MapBuilder.builder()
    .put("topContentSizeChange", MapBuilder.of("registrationName", "onContentSizeChange"))
    .put("topLayout", MapBuilder.of("registrationName", "onLayout"))
    .put("topLoadingError", MapBuilder.of("registrationName", "onLoadingError"))
    .put("topLoadingFinish", MapBuilder.of("registrationName", "onLoadingFinish"))
    .put("topLoadingStart", MapBuilder.of("registrationName", "onLoadingStart"))
    .put("topSelectionChange", MapBuilder.of("registrationName", "onSelectionChange"))
    .put("topMessage", MapBuilder.of("registrationName", "onMessage"))
    .build();
 }
}

3.实现的代码段 (JS端)

(node_modules\react-native\Libraries\Components\WebView\WebView.android.js)

在下面的代码中可以看到只有 onLoadingStart    和 onLoadingFinish 才会调用  updateNavigationState ,问题就出现在这了,由于我们的网页实现是React,只有一个页面啊!所以只会调用一次 onLoadingStart  和 onLoadingFinish 。再点击详情页并不会跳转到新页面,而是刷新原来的页面。所以也就没有 updateNavigationState 回调了。

class WebView extends React.Component {
 static propTypes = {  //给外部定义的可设置的属性
  ...ViewPropTypes,
  renderError: PropTypes.func,
  renderLoading: PropTypes.func,
  onLoad: PropTypes.func,
  //...
  }

 render() { //绘制页面内容
  //...
  var webView =
   <RCTWebView
    ref={RCT_WEBVIEW_REF}
    key="webViewKey"
    style={webViewStyles}
    source={resolveAssetSource(source)}
    onLoadingStart={this.onLoadingStart}
    onLoadingFinish={this.onLoadingFinish}
    onLoadingError={this.onLoadingError}/>;

  return (
   <View style={styles.container}>
    {webView}
    {otherView}
   </View>
  );
 }

 onLoadingStart = (event) => {
  var onLoadStart = this.props.onLoadStart;
  onLoadStart && onLoadStart(event);
  this.updateNavigationState(event);
 };

 onLoadingFinish = (event) => {
  var {onLoad, onLoadEnd} = this.props;
  onLoad && onLoad(event);
  onLoadEnd && onLoadEnd(event);
  this.setState({
   viewState: WebViewState.IDLE,
  });
  this.updateNavigationState(event);
 };

 updateNavigationState = (event) => {
  if (this.props.onNavigationStateChange) {
   this.props.onNavigationStateChange(event.nativeEvent);
  }
 };
}

var RCTWebView = requireNativeComponent('RCTWebView', WebView, {  //对应上面JAVA中的 ‘RCTWebView'
 nativeOnly: { messagingEnabled: PropTypes.bool, }, });


 module.exports = WebView;

2.解决方法

既然原因找到了,就容易解决了

解决方式:自定义WebView,添加 doUpdateVisitedHistory 处理,在每次导航变化的时候,通知JS。

1. 拷贝下图中的文件到我们自己项目中的Android代码目录下

详解react-native WebView 返回处理(非回调方法可解决)

拷贝完后的Android目录:

详解react-native WebView 返回处理(非回调方法可解决)

ReactWebViewManager.java中需要修改几个地方

public class ReactWebViewManager extends SimpleViewManager<WebView> {
 protected static final String REACT_CLASS = "RCTWebView1"; //此处修改一下名字

 protected static class ReactWebViewClient extends WebViewClient {
    @Override
    public void doUpdateVisitedHistory(WebView webView, String url, boolean isReload) {
      super.doUpdateVisitedHistory(webView, url, isReload);

      dispatchEvent(    //在导航变化的时候,dispatchEvent
          webView,
          new TopCanGoBackEvent(
              webView.getId(),
              createCanGoBackWebViewEvent(webView, url)));
    }
 }
}

TopCanGoBackEvent是我自己添加的一个Event,专门用来通知导航变化

TopCanGoBackEvent.java

public class TopCanGoBackEvent extends Event<TopCanGoBackEvent> {

 public static final String EVENT_NAME = "topChange"; 
 private WritableMap mEventData;

 public TopCanGoBackEvent(int viewId, WritableMap eventData) {
  super(viewId);
  mEventData = eventData;
 }

 @Override
 public String getEventName() {
  return EVENT_NAME;
 }

 @Override
 public boolean canCoalesce() {
  return false;
 }

 @Override
 public short getCoalescingKey() {
  // All events for a given view can be coalesced.
  return 0;
 }

 @Override
 public void dispatch(RCTEventEmitter rctEventEmitter) {
  rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mEventData);
 }
}

新建 ReactWebViewPage.java

public class ReactWebViewPackage implements ReactPackage {

  @Override
  public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {

    return Collections.emptyList();
  }

  @Override
  public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
    return Arrays.<ViewManager>asList(
        new ReactWebViewManager()
    );
  }
}

然后在MainApplication中添加这个模块

public class MainApplication extends Application implements ReactApplication {
  @Override
  protected List<ReactPackage> getPackages() {
   return Arrays.<ReactPackage>asList(
     new MainReactPackage(),
     new ReactWebViewPackage()  //WebView
   );
  }
}

以上就是Android需要修改的地方,ios我没有尝试过,应该大差不差同一个道理。

2. 拷贝下图中的文件到我们自己项目中的JS代码目录下,并修改一下名字

详解react-native WebView 返回处理(非回调方法可解决)

JS代码目录:

详解react-native WebView 返回处理(非回调方法可解决)

CustomWebView.android.js 有几个地方需要修改。

/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule CustomWebView  //此处需要修改名称
 */

var RCT_WEBVIEW_REF = 'webview1'; //此处需要修改名称

 render() {
  var webView =
   <NativeWebView
    onLoadingStart={this.onLoadingStart}
    onLoadingFinish={this.onLoadingFinish}
    onLoadingError={this.onLoadingError}
    onChange={this.onChange} //添加方法
   />;

  return (
   <View style={styles.container}>
    {webView}
    {otherView}
   </View>
  );
 }

 onChange = (event) => {  //添加方法
  this.updateNavigationState(event);
 };
}

var RCTWebView = requireNativeComponent('RCTWebView1', CustomWebView, CustomWebView.extraNativeComponentConfig); //修改名称

module.exports = CustomWebView; //修改名称

至此就完成自定义WebView模块。也可以解决网页是React实现,不能导航的问题。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
提高 DHTML 页面性能
Dec 25 Javascript
javascript 自动转到命名锚记
Jan 10 Javascript
javascript面向对象编程(一) 实例代码
Jun 25 Javascript
ASP.NET jQuery 实例17 通过使用jQuery validation插件校验ListBox
Feb 03 Javascript
在JavaScript中用getMinutes()方法返回指定的分时刻
Jun 10 Javascript
jQuery封装的屏幕居中提示信息代码
Jun 08 Javascript
微信js-sdk界面操作接口用法示例
Oct 12 Javascript
javascript 跨域问题以及解决办法
Jul 17 Javascript
JavaScript继承与聚合实例详解
Jan 22 Javascript
ES6知识点整理之函数对象参数默认值及其解构应用示例
Apr 17 Javascript
vue滚动tab跟随切换效果
Jun 29 Javascript
vue 使用rules对表单字段进行校验的步骤
Dec 25 Vue.js
Vue2.5通过json文件读取数据的方法
Feb 27 #Javascript
vue2.5.2使用http请求获取静态json数据的实例代码
Feb 27 #Javascript
jQuery幻灯片插件owlcarousel参数说明中文文档
Feb 27 #jQuery
关于ES6箭头函数中的this问题
Feb 27 #Javascript
vue中echarts3.0自适应的方法
Feb 26 #Javascript
swiper 解决动态加载数据滑动失效的问题
Feb 26 #Javascript
解决Angular.js中使用Swiper插件不能滑动的问题
Feb 26 #Javascript
You might like
PHP获取链表中倒数第K个节点的方法
2018/01/18 PHP
在JavaScript中遭遇级联表达式陷阱
2007/03/08 Javascript
用javascript来实现动画导航效果的代码
2007/12/16 Javascript
javascript不同页面传值的改进版
2008/09/30 Javascript
jQuery页面图片伴随滚动条逐渐显示的小例子
2013/03/21 Javascript
Jquery chosen动态设置值实例介绍
2013/08/08 Javascript
jquery等宽输出文字插件使用介绍
2013/09/18 Javascript
jquery实现网页查找功能示例分享
2014/02/12 Javascript
JavaScript改变CSS样式的方法汇总
2015/05/07 Javascript
简介JavaScript中的push()方法的使用
2015/06/09 Javascript
jquery实现倒计时小应用
2017/09/19 jQuery
ES6之模版字符串的具体使用
2018/05/17 Javascript
nodejs更新package.json中的dependencies依赖到最新版本的方法
2018/10/10 NodeJs
详解Next.js页面渲染的优化方案
2019/01/27 Javascript
vue el-table实现自定义表头
2019/12/11 Javascript
通过实例了解JS执行上下文运行原理
2020/06/17 Javascript
vue实现日历表格(element-ui)
2020/09/24 Javascript
[48:41]VP vs VG Supermajor小组赛 B组胜者组决赛 BO3 第二场 6.2
2018/06/03 DOTA
[00:47]DOTA2荣耀之路6:玩不了啦!
2018/05/30 DOTA
用Python脚本生成Android SALT扰码的方法
2013/09/18 Python
Python的Bottle框架中获取制定cookie的教程
2015/04/24 Python
Python 数据结构之队列的实现
2017/01/22 Python
理解Python中的绝对路径和相对路径
2017/08/30 Python
python中virtualenvwrapper安装与使用
2018/05/20 Python
Python UnboundLocalError和NameError错误根源案例解析
2018/10/31 Python
python中p-value的实现方式
2019/12/16 Python
OpenCV+Python3.5 简易手势识别的实现
2020/12/21 Python
日本最大的购物网站乐天市场国际版:Rakuten Global Market(支持中文)
2020/02/03 全球购物
毕业生实习鉴定
2013/12/11 职场文书
初中体育教学反思
2014/01/14 职场文书
小露珠教学反思
2014/04/30 职场文书
公司承诺书格式范文
2015/04/28 职场文书
大学生十八大感想
2015/08/11 职场文书
调研报告的主要写法
2019/04/18 职场文书
Python数据清洗工具之Numpy的基本操作
2021/04/22 Python
MYSQL中文乱码问题的解决方案
2022/06/14 MySQL