前言
在react native之前舍咖,大都采用hybird方案球订,目前WebView已經是app中不可或缺的一部分祈搜,采用react native之后依然需要支撐婚度。react native核心庫中就帶有WebView的封裝寻咒,但只是最基礎支撐,要擴展WebView的功能奋献,手段之一就是注入js钦听,俗稱jsBridge。
react native需要iOS7以上系統支撐叼架,因此注入js有兩種方案:
- 通過Request Url截獲解析。這是在iOS7之前采用的方式。
- 通過系統提供的javascriptCore通信方式。
這里我們討論第二種方案仪召,如果你對jsBridge不太熟悉咖摹,可以看這篇H5與native之間的通信。如果對javascriptCore不熟悉彼棍,可以看這個javascriptCore詳解。
既然已經有成熟的方案弛作,為什么還要寫這篇文章涕蜂?
還是那句話,最好不要修改react native原有代碼映琳,對以后的版本控制以及維護都不好宇葱,下面就來看看如何不修改react native實現需求,先放出項目地址刊头。
JS注入實現
要給WebView注入js,需要WebView資源加載完畢時诸尽,獲取WebView的JSContext原杂。也就是在- (void)webViewDidFinishLoad:(UIWebView *)webView
回調方法中通過[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]
獲取上下文,然后就可以為所欲為了您机。
那么關鍵點還是在:如何不侵入react native內部源碼
穿肄。
這時候category和swizzling隆重登場。首先使用swizzling替換原有webViewDidFinishLoad
方法:
+(void)load {
RCTSwapInstanceMethods([RCTWebView class], @selector(initWithFrame:), @selector(newInitWithFrame:));
RCTSwapInstanceMethods([RCTWebView class], @selector(webViewDidFinishLoad:), @selector(newWebViewDidFinishLoad:));
}
然后在新方法中际看,除了執(zhí)行原有邏輯之外咸产,再執(zhí)行js注入:
- (void)newWebViewDidFinishLoad:(UIWebView *)webView {
[self injectWebView: webView];
[self newWebViewDidFinishLoad: webView];
}
-(void) injectWebView: (UIWebView *) webView {
JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
if (context == nil) {
return;
}
//自定義注入對象
__weak typeof(self) weakSelf = self;
context[@"alert"] = ^(NSString *message) {
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message: message delegate:weakSelf cancelButtonTitle:@"cancel" otherButtonTitles:nil, nil];
[alert show];
});
};
}
這里定義了個簡單的函數alert
,用于調用native方法仲闽。下面驗證下注入是否有效脑溢,在js側定義WebView,并自動調用alert方法赖欣,為簡單起見WebView加載本地html:
const HTML = `<html>
<body>
<h1>Hello web view</h1>
<input type="button" value="call native" onclick='buttonAction()'/>
<script>
function buttonAction() {
alert('native button alert');
};
alert('native alert');
</script>
</body>
</html>`;
<WebView
ref={'webView'}
automaticallyAdjustContentInsets={false}
// style={styles.webView}
source={{html: HTML}}
// source={{url:'https://www.baidu.com'}}
javaScriptEnabled={true}
domStorageEnabled={true}
decelerationRate="normal"
onNavigationStateChange={this.onNavigationStateChange}
onShouldStartLoadWithRequest={this.onShouldStartLoadWithRequest}
// startInLoadingState={true}
scalesPageToFit={true}/>
看下效果:
![](https://raw.githubusercontent.com/xuwening/blog/master/mdFile/media/react/injectWebView.png)
單獨提一下屑彻,iOS中的WebView每次finishLoad時JSContext都會發(fā)生變化,所以要在每次load結束時重新注入js顶吮。還有一種情況是社牲,在資源加載過程中需要調用native接口,那么就要在WebView創(chuàng)建時同時獲取JSContext注入js:
- (instancetype)newInitWithFrame:(CGRect)frame {
RCTWebView *slf = [self newInitWithFrame: frame];
UIWebView *webView = [slf valueForKey:@"_webView"];
[self injectWebView: webView];
return slf;
}
RCTWebView自身提供了一個屬性injectedJavaScript
悴了,用于資源加載完畢時自動執(zhí)行的一段js腳本搏恤。比如你需要把jsBridge的js側代碼庫注入到目標頁,可以使用這個屬性湃交。
react native的JSContext獲取熟空、注入
react native項目自身使用的JSContext與WebView的JSContext不是一回事兒,也就是說你在react native的JSContext中注入接口巡揍,WebView是無法訪問到的痛阻,反之亦然。如果你需要將js接口在react中和WebView中能同時使用腮敌,必須兩邊都要注入阱当。
react native關于JSContext的封裝在RCTJSCExecutor
中俏扩,它實現了一個通知RCTJavaScriptContextCreatedNotification
,當JSContext創(chuàng)建完畢弊添,還未加載main.jsbundle時會發(fā)送通知录淡,JSContext作為通知的參數傳遞過來。
于是油坝,一切就簡單了嫉戚,我們注冊這個通知獲取JSContext:
+(void)load {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(recieveNotification:) name:RCTJavaScriptContextCreatedNotification object:nil];
}
+(void) recieveNotification: (NSNotification *) notification {
JSContext *context = notification.object;
__weak typeof(self) weakSelf = self;
//這里由js線程調用,所以UI操作需要指定主線程
context[@"alert"] = ^(NSString *message) {
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:@"native alert" delegate:weakSelf cancelButtonTitle:@"cancel" otherButtonTitles:nil, nil];
[alert show];
});
};
}
功能實現了澈圈,不過這里要提醒一句彬檀,react native實現了一套js與native模塊化通信的機制,雖然我們依然可以給react通過JSContext注入的方式瞬女,但不建議這么使用窍帝,通過react native提供的模塊導出方法才是正道。
關于react native模塊的知識诽偷,可以參考react native之模塊
如果需要理解react native通信機制原理坤学,可以參考react native之OC與js之間交互
這些都偏源碼,可能有的讀者不喜歡看报慕,后面會單獨出一篇react native模塊開發(fā)
的章節(jié)深浮。