一、各平臺Web控件簡單使用
當使用某個平臺開發(fā)app時候,難免有需要引入網(wǎng)頁的場景,使用方法不外乎引入對應平臺的WebView控件,同時設置好URL地址即可:
iOS平臺:使用UIWebView,WKWebView,并調(diào)用對應的控件方法來實現(xiàn)
- (void)createWebView {
// 1.創(chuàng)建控件
UIWebView *webView = [[UIWebView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[self.view addSubview:self.webView];
// 2.請求URL
NSURL *url = [NSURL URLWithString:@"http://www.github.com"]
// 3.創(chuàng)建請求
NSMutableURLRequest *request =[NSMutableURLRequest requestWithURL:url]
// 4.加載頁面
[webView loadRequest:request];
}
其中UIWebView是早期的選擇,但由于占用過多的內(nèi)存以及性能上的原因,從IOS8開始用WKWebView來取代笨重的UIWebVie,創(chuàng)建的方式僅僅將UIWebView替換成WKWebView即可
RN平臺:使用RN里的WebView控件,并設置控件的屬性來實現(xiàn)(iOS中總是方法調(diào)用,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,控件的實現(xiàn)往往離不開 UIKit 等框架,調(diào)用的也是原生的 Objective-C 代碼。那WebView控件是如何封裝呢?查看任意RN工程下/node_modules/Racct-native/React/Views/RCTWebView.m的源代碼:
@implementation RCTWebView {
UIWebView *_webView;
NSString *_injectedJavaScript;
}
可以看出它是基于UIWebView來實現(xiàn)的,它的性能很大程度上就取決于UIWebVIew的性能,猜測不久會替換成WKWebView的實現(xiàn)方式吧。
二、各平臺與Web的通訊
稍微復雜點的應用已經(jīng)不滿足頁面的展示,往往需要平臺和頁面進行通訊,比如想在一個展示用戶信息的Web頁面對用戶進行關注操作,大致有三個環(huán)節(jié):
- Web調(diào)用平臺的FollowUser接口,同時約定好結果的CallBack
- 平臺在該接口進行網(wǎng)絡請求并執(zhí)行相應操作
- 平臺調(diào)用Web頁面的CallBack函數(shù),并把結果作為參數(shù)傳遞回去
而實現(xiàn)的基礎就是各個平臺如何Web進行通訊,對平臺而言,就是要實現(xiàn)監(jiān)聽收到Web消息(也稱作Web調(diào)用平臺),以及如何調(diào)用Web的方法。
iOS平臺WKWebView:
- 平臺監(jiān)聽Web:平臺通過捕獲Web端JS調(diào)用alert、confirm、prompt的信息來實現(xiàn)
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt
defaultText:(nullable NSString *)defaultText
initiatedByFrame:(WKFrameInfo *)frame
completionHandler:(void (^)(NSString * _Nullable result))completionHandler;
由于已經(jīng)在原生環(huán)境,故可以在該函數(shù)中去執(zhí)行平臺的一些操作,這里有意思的是你通過在completionHandler傳回信息,在Web端可以在alert關閉后獲取到對應的數(shù)據(jù),這樣可以同步方式調(diào)用原生的效果。
- 平臺調(diào)用Web:
- (void)evaluateJavaScript:(NSString *)javaScriptString
completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
基于上面兩個函數(shù),就能進行一定的封裝,比如Web端約定好調(diào)用的協(xié)議,也就是prompt字符串的如何解析等等,具體業(yè)務場景會在另外一篇中介紹,這里僅截取一段:
//navite調(diào)用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:
- 平臺監(jiān)聽Web:js中指定document.location的值,由于webView的重定向原理,OC中的shouldStartLoadWithRequest函數(shù)將會捕獲到處理請求
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType;
- 平臺調(diào)用Web:
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
WebViewJavascript就是基于此進行封裝,這也是和UIWebView最大的區(qū)別,不過兩種怎么看都有種黑科技的感覺
RN平臺WebView:
- 平臺監(jiān)聽Web:
首先RN需要設置onMessage屬性來監(jiān)聽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函數(shù)里你就可以統(tǒng)一處理收到的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數(shù)據(jù),并調(diào)用用戶綁定的onMessge方法
- 平臺調(diào)用Web
在WebView的ref屬性執(zhí)行之后的任意地方你就可以調(diào)用postMessage向WebView發(fā)送消息
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實際上是通過調(diào)用UIWebView的stringByEvaluatingJavaScriptFromString函數(shù)來實現(xiàn)
Web端HTML頁面部分
- Web監(jiān)聽平臺
window.document.addEventListener('message', function (e) {
const message = e.data
})
- Web調(diào)用平臺
const message: string = 'Web->RN'
this.webview.postMessage(message)
三、總結:
- RN中的WebView控件是基本上是對原生控件的封裝,而原生控件蘋果會本身會不斷進行更新,那RN自然也是需要持續(xù)更新來保證最佳的性能
- 如果需要對RN通訊進行一定程度的封裝,也要謹慎的采用不入侵的方式進行封裝。
四、備注:
- onMessage/postMessage是RN從 0.37 版才支持的。支持android和iOS,以上代碼僅提供iOS,android原理差不多。
附上項目Demo地址
本人初學RN,歡迎留言指正,也歡迎關注收藏