小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
前言
Hello 大家好! 我是前端 无名
背景
我们的H5网页经常嵌套在react-native(RN端)提供的webView中运行的。
有一次,经过几天的日日夜夜,终于把产品小姐姐的需求给做完了,按时进入测试。
某一天,测试小哥哥过来:
你这个网页,第一次进入,正常,点击刷新按钮以后,退出,重新打开H5,一直在loading,必须强杀应用才能正常展示。说着拿出手机给我演示。
我看了整个过程以后,点击刷新以后,再次进入,看到一眼loading,这loading不是我们的啊,再看看,loading是透明的,我们的H5其实已经加载出来了。这个loading是RN端的loading 啊,这个我也不好排查,我不知道RN端loading消失的时机,于是跟着测试小哥哥一起去找RN端。
RN端小哥: 这个是你们前端的问题吧,你看看人家别人的网页都没有问题。
前端我: 这个你们的loading没有消失,能看看是资源加载失败还是什么其他问题吗?。
.....开始撕逼
排查问题
- RN端的webView代码
我们可以看到RN端是直接使用webView的renderLoading去渲染的,loading的消失其实是交个react-native-webview去控制的。
然后查看loadEnd正常调用,然后就让RN端小哥哥修改了loading 处理方式,变为自己主动控制。
RN端webView嵌套结构如下:
return <View> <WebView /> <LoadingView /></View>
这样以后刚进入默认loading直接渲染,监听到loadEnd事件以后,修改Loading状态,去除loading组件
修改完以后,完美解决,H5每次都可以正常渲染。
事情就这么解决了???...
RN端小哥说,这个不能这么改,很多地方都用到了这个WebView组件,这样改会不会有其他影响,你看别的H5都没有问题。
- react-native-webView源码
RN端小哥哥不修改,我们怎么办呢?RN的loading不消失,作为H5的我们怎么解决呢??
哎,愁死了!
看看react-native-webView的源码去,看看rendLoading消失的时机到底是什么时候!
问了下RN小哥哥当前在用的react-native-webView版本,然后开始看代码了。
源码目录:src/WebView.android.tsx
import React from 'react';
import {
Image,
...
} from 'react-native';
...省略
/**
* Renders a native WebView.
*/
class WebView extends React.Component<AndroidWebViewProps, State> {
startUrl: string | null = null;
state: State = {
viewState: this.props.startInLoadingState ? 'LOADING' : 'IDLE',
lastErrorEvent: null,
};
reload = () => {
this.setState({
viewState: 'LOADING',
});
UIManager.dispatchViewManagerCommand(
this.getWebViewHandle(),
this.getCommands().reload,
undefined
);
};
updateNavigationState = (event: WebViewNavigationEvent) => {
if (this.props.onNavigationStateChange) {
this.props.onNavigationStateChange(event.nativeEvent);
}
};
//重点
onLoadingStart = (event: WebViewNavigationEvent) => {
const { onLoadStart } = this.props;
const { nativeEvent: { url } } = event;
//重点
this.startUrl = url;
if (onLoadStart) {
onLoadStart(event);
}
this.updateNavigationState(event);
};
onLoadingError = (event: WebViewErrorEvent) => {
event.persist(); // persist this event because we need to store it
const { onError, onLoadEnd } = this.props;
if (onError) {
onError(event);
} else {
console.warn('Encountered an error loading page', event.nativeEvent);
}
if (onLoadEnd) {
onLoadEnd(event);
}
if (event.isDefaultPrevented()) return;
//重点
this.setState({
lastErrorEvent: event.nativeEvent,
viewState: 'ERROR',
});
};
onLoadingFinish = (event: WebViewNavigationEvent) => {
const { onLoad, onLoadEnd } = this.props;
const { nativeEvent: { url } } = event;
if (onLoad) {
onLoad(event);
}
if (onLoadEnd) {
onLoadEnd(event);
}
//重点
if (url === this.startUrl) {
this.setState({
viewState: 'IDLE',
});
}
this.updateNavigationState(event);
};
onLoadingProgress = (event: WebViewProgressEvent) => {
const { onLoadProgress } = this.props;
const { nativeEvent: { progress } } = event;
//重点
if (progress === 1) {
this.setState((state) => {
if (state.viewState === 'LOADING') {
return { viewState: 'IDLE' };
}
return null;
});
}
if (onLoadProgress) {
onLoadProgress(event);
}
};
render() {
const {
onMessage,
onShouldStartLoadWithRequest: onShouldStartLoadWithRequestProp,
originWhitelist,
renderError,
renderLoading,
source,
style,
containerStyle,
nativeConfig = {},
...otherProps
} = this.props;
let otherView = null;
//重点
if (this.state.viewState === 'LOADING') {
otherView = (renderLoading || defaultRenderLoading)();
} else if (this.state.viewState === 'ERROR') {
const errorEvent = this.state.lastErrorEvent;
invariant(errorEvent != null, 'lastErrorEvent expected to be non-null');
otherView = (renderError || defaultRenderError)(
errorEvent.domain,
errorEvent.code,
errorEvent.description,
);
} else if (this.state.viewState !== 'IDLE') {
console.error(
`RNCWebView invalid state encountered: ${this.state.viewState}`,
);
}
...
const webView = (
<NativeWebView
key="webViewKey"
{...otherProps}
messagingEnabled={typeof onMessage === 'function'}
messagingModuleName={this.messagingModuleName}
onLoadingError={this.onLoadingError}
onLoadingFinish={this.onLoadingFinish}
onLoadingProgress={this.onLoadingProgress}
onLoadingStart={this.onLoadingStart}
onHttpError={this.onHttpError}
onRenderProcessGone={this.onRenderProcessGone}
onMessage={this.onMessage}
onShouldStartLoadWithRequest={this.onShouldStartLoadWithRequest}
ref={this.webViewRef}
source={resolveAssetSource(source as ImageSourcePropType)}
style={webViewStyles}
{...nativeConfig.props}
/>
);
return (
<View style={webViewContainerStyle}>
{webView}
{otherView}
</View>
);
}
}
export default WebView;
- 分析源码 从源码中我们可以看出renderLoading 的消失主要看viewState的状态值。
我们就重点关注哪些地方修改了viewState状态值。
viewState默认是loading, 在onLoadingError,onLoadingProgress,onLoadingFinish会修改。
并且只有viewState==='IDLE', H5也才能正常渲染。
由于我们看到onLoadEnd每次都会调用(H5成功失败都会调用),那为什么再次刷新loading不消失呢?
原因就处在url===this.startUrl,由于我们采用的是HashRouter,并且懒加载。打开H5的链接是:https:xxxx.a/index.html 这样默认打开我们的home页:https:xxxx.a/index.html#/
这导致我们starturl与url不一致,然后RN的renderLoading不能正常消失。
问题来了?为什么不点击H5网页的刷新按钮正常加载啊?
不点击刷新按钮,每次打开都会调用:onLoadingProgress函数,progress为1,这个时候同样是viewState==='IDLE',正常加载。
点击H5网页刷新按钮,做了哪些操作呢??
这是只是重新加载网页,网页后面加了一个ticket,不用缓存。 结果window.location.replace以及window.location.href都不行。
只要在H5网页中刷新了,然后退出H5重新打开webView就会一直loading,并且不强杀应用还不行。
排查是刷新以后,onLoadingProgress 中progress不是1,所以修改不了ViewState,。
- 解决方案
原因找到了,怎么解决呢,RN小哥哥不修改代码,H5怎么办呢?
解决方案:我们只要保障startUrl和loadEnd后的url一致不就可以了吗。 所以修改默认打开的h5网页地址为home页:https:xxxx.a/index.html#/
后来查看react-native-webview 的 issues 发现,react-native-webview后面升级已经解决这个问题了。
后语
欢迎大家多提意见。项目模板在不断优化,一赞一回!欢迎评论。