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 (
      
    );
  }
}

我们知道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 (  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,欢迎留言指正,也欢迎关注收藏

你可能感兴趣的:(React Native(三)WebView使用以及双向通讯)