詳解react-native WebView 返回處理(非回調(diào)方法可解決)
1.前言
項(xiàng)目中有些頁(yè)面內(nèi)容是變更比較頻繁的,這些頁(yè)面我們會(huì)考慮用 網(wǎng)頁(yè) 來解決。
在RN項(xiàng)目中提供一個(gè)公用的Web頁(yè),如果是網(wǎng)頁(yè)內(nèi)容,就跳轉(zhuǎn)到這個(gè)界面展示。
此時(shí)會(huì)有一個(gè)問題是,網(wǎng)頁(yè)會(huì)有一級(jí)頁(yè)面,二級(jí)頁(yè)面,這就會(huì)設(shè)計(jì)到導(dǎo)航欄返回鍵的處理(以及在Android上返回鍵的處理)。
這個(gè)問題,在RN官網(wǎng)就可找到解決方式。就是用 onNavigationStateChange 這個(gè)回調(diào)方法記錄當(dāng)前的導(dǎo)航狀態(tài),從而判斷是返回上一級(jí)頁(yè)面還是退出這個(gè)網(wǎng)頁(yè),回到App的其他界面。
但是,當(dāng)網(wǎng)頁(yè)的實(shí)現(xiàn)是React時(shí),就會(huì)有問題了,你會(huì)發(fā)現(xiàn),當(dāng)頁(yè)面跳轉(zhuǎn)的時(shí)候,onNavigationStateChange這個(gè)回調(diào)方法沒有回調(diào)?。。≡趺捶仕模?!
一開始嘗試了把網(wǎng)頁(yè)地址換成百度的,可以接收回調(diào),一切都運(yùn)行的很好,可是換成我們的鏈接就不行,所以就把鍋甩給了后臺(tái),以為是React哪邊寫的不對(duì)。
因?yàn)樯弦粋€(gè)項(xiàng)目時(shí)間緊,沒有時(shí)間好好去看一下源碼,就想了一個(gè)不是很完善的解決方案,就是網(wǎng)頁(yè)用js來回調(diào)App來告知現(xiàn)在的導(dǎo)航狀態(tài),這樣的解決方式顯示是不友好的。
現(xiàn)在稍微有點(diǎn)時(shí)間看了源碼才知道真正原因。
2.原因
下面就分析一下這個(gè)問題的原因和我的解決方式。
1.首先,先找到源碼的位置。
node_modules\react-native\ReactAndroid\src\main\java\com\facebook\react\views\webview
node_modules\react-native\Libraries\Components\WebView
目錄結(jié)構(gòu)是這樣的:

2.實(shí)現(xiàn)的代碼段 (JAVA端)
RN的實(shí)際運(yùn)行代碼都是原生代碼,所以,像WebView組件的一些事件回調(diào),其實(shí)都是原生代碼中的回調(diào)觸發(fā)的。如下
(ReactWebViewManager.java) rn版本0.47.1
protected static class ReactWebViewClient extends WebViewClient { //WebViewClient就是我們?cè)趯慉ndroid原生代碼時(shí),監(jiān)聽網(wǎng)頁(yè)加載情況使用的工具。
protected static final String REACT_CLASS = "RCTWebView"; //定義的原生組件名,在后面JS中會(huì)對(duì)應(yīng)到。
//...
@Override
public void onPageStarted(WebView webView, String url, Bitmap favicon) { //有很多回調(diào)方法,此處只舉一例
super.onPageStarted(webView, url, favicon);
mLastLoadFailed = false;
dispatchEvent(
webView,
new TopLoadingStartEvent( //自己定義的時(shí)間,dispatch后,事件會(huì)傳給js
webView.getId(),
createWebViewEvent(webView, url)));
}
//...
}
(ReactWebViewManager.java) rn版本0.43.3 ,RN不同版本會(huì)有代碼調(diào)整,所以RN升級(jí)的時(shí)候,需要仔細(xì)的回歸測(cè)試。
protected static class ReactWebViewClient extends WebViewClient { //WebViewClient就是我們?cè)趯慉ndroid原生代碼時(shí),監(jiān)聽網(wǎng)頁(yè)加載情況使用的工具。
protected static final String REACT_CLASS = "RCTWebView"; //定義的原生組件名,在后面JS中會(huì)對(duì)應(yīng)到。
//...
@Override
public void onPageStarted(WebView webView, String url, Bitmap favicon) { //有很多回調(diào)方法,此處只舉一例
super.onPageStarted(webView, url, favicon);
mLastLoadFailed = false;
dispatchEvent(
webView,
new TopLoadingStartEvent( //自己定義的時(shí)間,dispatch后,事件會(huì)傳給js
webView.getId(),
createWebViewEvent(webView, url)));
}
@Override
public void doUpdateVisitedHistory(WebView webView, String url, boolean isReload) { //坑在這,這里就是導(dǎo)航有變化的時(shí)候會(huì)回調(diào)在這個(gè)版本是有這個(gè)處理的,但是不知道在哪個(gè)版本刪掉了 -.-
super.doUpdateVisitedHistory(webView, url, isReload);
dispatchEvent(
webView,
new TopLoadingStartEvent(
webView.getId(),
createWebViewEvent(webView, url)));
}
//...
}
(TopLoadingStartEvent.java) 回調(diào)JS的Event
public class TopLoadingStartEvent extends Event<TopLoadingStartEvent> {
public static final String EVENT_NAME = "topLoadingStart"; //對(duì)應(yīng)方法是onLoadingStart, 因?yàn)閷?duì)RN的結(jié)構(gòu)不熟悉,在此處花了很長(zhǎng)時(shí)間研究是怎么對(duì)應(yīng)的,最后找到了定義對(duì)應(yīng)的文件
private WritableMap mEventData;
public TopLoadingStartEvent(int viewId, WritableMap eventData) {
super(viewId);
mEventData = eventData;
}
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public boolean canCoalesce() {
return false;
}
@Override
public short getCoalescingKey() {
// All events for a given view can be coalesced.
return 0;
}
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mEventData);
}
}
(node_modules\react-native\ReactAndroid\src\main\java\com\facebook\react\uimanager\UIManagerModuleConstants.java)
這個(gè)文件里,定義了對(duì)應(yīng)關(guān)系
/**
* Constants exposed to JS from {@link UIManagerModule}.
*/
/* package */ class UIManagerModuleConstants {
/* package */ static Map getDirectEventTypeConstants() {
return MapBuilder.builder()
.put("topContentSizeChange", MapBuilder.of("registrationName", "onContentSizeChange"))
.put("topLayout", MapBuilder.of("registrationName", "onLayout"))
.put("topLoadingError", MapBuilder.of("registrationName", "onLoadingError"))
.put("topLoadingFinish", MapBuilder.of("registrationName", "onLoadingFinish"))
.put("topLoadingStart", MapBuilder.of("registrationName", "onLoadingStart"))
.put("topSelectionChange", MapBuilder.of("registrationName", "onSelectionChange"))
.put("topMessage", MapBuilder.of("registrationName", "onMessage"))
.build();
}
}
3.實(shí)現(xiàn)的代碼段 (JS端)
(node_modules\react-native\Libraries\Components\WebView\WebView.android.js)
在下面的代碼中可以看到只有 onLoadingStart 和 onLoadingFinish 才會(huì)調(diào)用 updateNavigationState ,問題就出現(xiàn)在這了,由于我們的網(wǎng)頁(yè)實(shí)現(xiàn)是React,只有一個(gè)頁(yè)面啊!所以只會(huì)調(diào)用一次 onLoadingStart 和 onLoadingFinish 。再點(diǎn)擊詳情頁(yè)并不會(huì)跳轉(zhuǎn)到新頁(yè)面,而是刷新原來的頁(yè)面。所以也就沒有 updateNavigationState 回調(diào)了。
class WebView extends React.Component {
static propTypes = { //給外部定義的可設(shè)置的屬性
...ViewPropTypes,
renderError: PropTypes.func,
renderLoading: PropTypes.func,
onLoad: PropTypes.func,
//...
}
render() { //繪制頁(yè)面內(nèi)容
//...
var webView =
<RCTWebView
ref={RCT_WEBVIEW_REF}
key="webViewKey"
style={webViewStyles}
source={resolveAssetSource(source)}
onLoadingStart={this.onLoadingStart}
onLoadingFinish={this.onLoadingFinish}
onLoadingError={this.onLoadingError}/>;
return (
<View style={styles.container}>
{webView}
{otherView}
</View>
);
}
onLoadingStart = (event) => {
var onLoadStart = this.props.onLoadStart;
onLoadStart && onLoadStart(event);
this.updateNavigationState(event);
};
onLoadingFinish = (event) => {
var {onLoad, onLoadEnd} = this.props;
onLoad && onLoad(event);
onLoadEnd && onLoadEnd(event);
this.setState({
viewState: WebViewState.IDLE,
});
this.updateNavigationState(event);
};
updateNavigationState = (event) => {
if (this.props.onNavigationStateChange) {
this.props.onNavigationStateChange(event.nativeEvent);
}
};
}
var RCTWebView = requireNativeComponent('RCTWebView', WebView, { //對(duì)應(yīng)上面JAVA中的 ‘RCTWebView'
nativeOnly: { messagingEnabled: PropTypes.bool, }, });
module.exports = WebView;
2.解決方法
既然原因找到了,就容易解決了
解決方式:自定義WebView,添加 doUpdateVisitedHistory 處理,在每次導(dǎo)航變化的時(shí)候,通知JS。
1. 拷貝下圖中的文件到我們自己項(xiàng)目中的Android代碼目錄下

拷貝完后的Android目錄:

ReactWebViewManager.java中需要修改幾個(gè)地方
public class ReactWebViewManager extends SimpleViewManager<WebView> {
protected static final String REACT_CLASS = "RCTWebView1"; //此處修改一下名字
protected static class ReactWebViewClient extends WebViewClient {
@Override
public void doUpdateVisitedHistory(WebView webView, String url, boolean isReload) {
super.doUpdateVisitedHistory(webView, url, isReload);
dispatchEvent( //在導(dǎo)航變化的時(shí)候,dispatchEvent
webView,
new TopCanGoBackEvent(
webView.getId(),
createCanGoBackWebViewEvent(webView, url)));
}
}
}
TopCanGoBackEvent是我自己添加的一個(gè)Event,專門用來通知導(dǎo)航變化
TopCanGoBackEvent.java
public class TopCanGoBackEvent extends Event<TopCanGoBackEvent> {
public static final String EVENT_NAME = "topChange";
private WritableMap mEventData;
public TopCanGoBackEvent(int viewId, WritableMap eventData) {
super(viewId);
mEventData = eventData;
}
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public boolean canCoalesce() {
return false;
}
@Override
public short getCoalescingKey() {
// All events for a given view can be coalesced.
return 0;
}
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mEventData);
}
}
新建 ReactWebViewPage.java
public class ReactWebViewPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new ReactWebViewManager()
);
}
}
然后在MainApplication中添加這個(gè)模塊
public class MainApplication extends Application implements ReactApplication {
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new ReactWebViewPackage() //WebView
);
}
}
以上就是Android需要修改的地方,ios我沒有嘗試過,應(yīng)該大差不差同一個(gè)道理。
2. 拷貝下圖中的文件到我們自己項(xiàng)目中的JS代碼目錄下,并修改一下名字

JS代碼目錄:

CustomWebView.android.js 有幾個(gè)地方需要修改。
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule CustomWebView //此處需要修改名稱
*/
var RCT_WEBVIEW_REF = 'webview1'; //此處需要修改名稱
render() {
var webView =
<NativeWebView
onLoadingStart={this.onLoadingStart}
onLoadingFinish={this.onLoadingFinish}
onLoadingError={this.onLoadingError}
onChange={this.onChange} //添加方法
/>;
return (
<View style={styles.container}>
{webView}
{otherView}
</View>
);
}
onChange = (event) => { //添加方法
this.updateNavigationState(event);
};
}
var RCTWebView = requireNativeComponent('RCTWebView1', CustomWebView, CustomWebView.extraNativeComponentConfig); //修改名稱
module.exports = CustomWebView; //修改名稱
至此就完成自定義WebView模塊。也可以解決網(wǎng)頁(yè)是React實(shí)現(xiàn),不能導(dǎo)航的問題。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
React網(wǎng)絡(luò)請(qǐng)求發(fā)起方法詳細(xì)介紹
在編程開發(fā)中,網(wǎng)絡(luò)數(shù)據(jù)請(qǐng)求是必不可少的,這篇文章主要介紹了React網(wǎng)絡(luò)請(qǐng)求發(fā)起方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-09-09
create-react-app中添加less支持的實(shí)現(xiàn)
這篇文章主要介紹了react.js create-react-app中添加less支持的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11
react?事項(xiàng)懶加載的三種方法及使用場(chǎng)景
這篇文章主要介紹了react?事項(xiàng)懶加載的三種方法及使用場(chǎng)景,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07
react-native?父函數(shù)組件調(diào)用類子組件的方法(實(shí)例詳解)
這篇文章主要介紹了react-native?父函數(shù)組件調(diào)用類子組件的方法,通過詳細(xì)步驟介紹了React 函數(shù)式組件之父組件調(diào)用子組件的方法,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-09-09
react-pdf?打造在線簡(jiǎn)歷生成器的示例代碼
本文主要介紹了react-pdf?打造在線簡(jiǎn)歷生成器的示例代碼,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02
react antd實(shí)現(xiàn)動(dòng)態(tài)增減表單
antd是react流行的ui框架庫(kù),本文主要介紹了react antd實(shí)現(xiàn)動(dòng)態(tài)增減表單,分享給大家,感興趣的小伙伴們可以參考一下2021-06-06

