React Native(三)WebView使用以及双向通讯

一、各平台Web控件简单使用

当使用某个平台开发app时候,难免有需要引入网页的场景,使用方法不外乎引入对应平台的WebView控件,同时设置好URL地址即可:

iOS平台:使用UIWebView,WKWebView,并调用对应的控件方法来实现
- (void)createWebView { 
    // 1.创建控件
    UIWebView *webView = [[UIWebView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    [self.view addSubview:self.webView];
    // 2.请求URL
    NSURL *url = [NSURL URLWithString:@"http://www.github.com"]
    // 3.创建请求
    NSMutableURLRequest *request =[NSMutableURLRequest requestWithURL:url]
    // 4.加载页面
    [webView loadRequest:request];
}

其中UIWebView是早期的选择,但由于占用过多的内存以及性能上的原因,从IOS8开始用WKWebView来取代笨重的UIWebVie,创建的方式仅仅将UIWebView替换成WKWebView即可

RN平台:使用RN里的WebView控件,并设置控件的属性来实现(iOS中总是方法调用,RN总是属性设置)
import React, { Component } from 'react';
import { WebView } from 'react-native';

class MyWeb extends Component {
  render() {
    return (
      <WebView
        source={{uri: 'https://github.com'}}
      />
    );
  }
}

我们知道React Native不像Hybrid,控件的实现往往离不开 UIKit 等框架,调用的也是原生的 Objective-C 代码。那WebView控件是如何封装呢?查看任意RN工程下/node_modules/Racct-native/React/Views/RCTWebView.m的源代码:

@implementation RCTWebView {
  UIWebView *_webView;
  NSString *_injectedJavaScript;
}

可以看出它是基于UIWebView来实现的,它的性能很大程度上就取决于UIWebVIew的性能,猜测不久会替换成WKWebView的实现方式吧。

二、各平台与Web的通讯

稍微复杂点的应用已经不满足页面的展示,往往需要平台和页面进行通讯,比如想在一个展示用户信息的Web页面对用户进行关注操作,大致有三个环节:

  • Web调用平台的FollowUser接口,同时约定好结果的CallBack
  • 平台在该接口进行网络请求并执行相应操作
  • 平台调用Web页面的CallBack函数,并把结果作为参数传递回去

而实现的基础就是各个平台如何Web进行通讯,对平台而言,就是要实现监听收到Web消息(也称作Web调用平台),以及如何调用Web的方法。

iOS平台WKWebView:
  • 平台监听Web:平台通过捕获Web端JS调用alert、confirm、prompt的信息来实现
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt 
     defaultText:(nullable NSString *)defaultText 
     initiatedByFrame:(WKFrameInfo *)frame 
     completionHandler:(void (^)(NSString * _Nullable result))completionHandler;

由于已经在原生环境,故可以在该函数中去执行平台的一些操作,这里有意思的是你通过在completionHandler传回信息,在Web端可以在alert关闭后获取到对应的数据,这样可以同步方式调用原生的效果。

  • 平台调用Web:
- (void)evaluateJavaScript:(NSString *)javaScriptString       
    completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;

基于上面两个函数,就能进行一定的封装,比如Web端约定好调用的协议,也就是prompt字符串的如何解析等等,具体业务场景会在另外一篇中介绍,这里仅截取一段:

//navite调用Web
//ex:window['NEW_GAME']['onLikeCommentSuccess'](12342)
- (void)callHandler:(NSString *)methodName arg:(NSString*)arg
  completionHandler:(void (^)(NSString *  _Nullable))completionHandler {
    NSString *script = [NSString stringWithFormat:@"window['NEW_GAME']['%@'](%@)" , methodName, arg];
    [self.webView evaluateJavaScript:script completionHandler:^(id value,NSError * error){
        if(completionHandler) completionHandler(value);
    }];
}
iOS平台UIWebView:
  • 平台监听Web:js中指定document.location的值,由于webView的重定向原理,OC中的shouldStartLoadWithRequest函数将会捕获到处理请求
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request 
    navigationType:(UIWebViewNavigationType)navigationType;
  • 平台调用Web:
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

WebViewJavascript就是基于此进行封装,这也是和UIWebView最大的区别,不过两种怎么看都有种黑科技的感觉

RN平台WebView:
  • 平台监听Web:

首先RN需要设置onMessage属性来监听Web的消息

import * as React from 'react'
import { WebView } from 'react-native'

class TestWeb extends React.Component {
    webview: WebView
    handleMessage = (evt: any) => {
        const message = evt.nativeEvent.data
    }
    render() {
        return ( <WebView
                ref={webview => this.webview = webview}
                onMessage={this.handleMessage}
            />
        )
    }
}

然后在handleMessage函数里你就可以统一处理收到的WebView的消息了,这里的onMessage属性是什么呢?还是RCTWebView.m文件

@property (nonatomic, copy) RCTDirectEventBlock onMessage;
- (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
 navigationType:(UIWebViewNavigationType)navigationType{
    BOOL isJSNavigation = [request.URL.scheme isEqualToString:RCTJSNavigationScheme];
    ...
    if (isJSNavigation && [request.URL.host isEqualToString:kPostMessageHost]) {
       [_webView stringByEvaluatingJavaScriptFromString:source];
       _onMessage(event);
    }
}

RN通过判断请求地址是否用作JS通讯,如果是,则取出event数据,并调用用户绑定的onMessge方法

  • 平台调用Web

在WebView的ref属性执行之后的任意地方你就可以调用postMessage向WebView发送消息

const message: string = 'RN->Web'     
this.webview.postMessage(message);

这里的postMessage是什么呢?还是RCTWebView.m文件

- (void)postMessage:(NSString *)message {
  NSDictionary *eventInitDict = @{
    @"data": message,
  };
  NSString *source = [NSString
    stringWithFormat:@"document.dispatchEvent(new MessageEvent('message', %@));",
    RCTJSONStringify(eventInitDict, NULL)
  ];
  [_webView stringByEvaluatingJavaScriptFromString:source];
}

可见postMessage实际上是通过调用UIWebView的stringByEvaluatingJavaScriptFromString函数来实现
Web端HTML页面部分

  • Web监听平台
window.document.addEventListener('message', function (e) {
    const message = e.data
})
  • Web调用平台
const message: string = 'Web->RN'
this.webview.postMessage(message)

三、总结:

  • RN中的WebView控件是基本上是对原生控件的封装,而原生控件苹果会本身会不断进行更新,那RN自然也是需要持续更新来保证最佳的性能
  • 如果需要对RN通讯进行一定程度的封装,也要谨慎的采用不入侵的方式进行封装。

四、备注:

  • onMessage/postMessage是RN从 0.37 版才支持的。支持android和iOS,以上代码仅提供iOS,android原理差不多。

附上项目Demo地址
本人初学RN,欢迎留言指正,也欢迎关注收藏

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,613评论 4 59
  • 前言 关于UIWebView的介绍,相信看过上文的小伙伴们,已经大概清楚了吧,如果有问题,欢迎提问。 本文是本系列...
    CoderLF阅读 8,854评论 2 12
  • Vue v2.0 自定义指令 顾名思义,这个是为vue移动端web项目打造的触摸反馈指令,高度模拟App反馈效果;...
    昨天之前阅读 1,934评论 0 3
  • 有时候真的有种想放弃的感觉。但是,又不知道自己放弃以后,真的会变得比现在好吗?或者放弃以后,有能力找到更好的吗...
    Syusuke阅读 450评论 1 2