一、各平台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,欢迎留言指正,也欢迎关注收藏