react-native-webview onShouldStartLoadWithRequest返回false后加载失效
问题描述
在React-Native项目中使用react-native-webview组件时,通过onShouldStartLoadWithRequest回调做URL请求校验,逻辑如下:
- 当请求URL不等于预设变量
myvarUrl时,回调返回true,URL正常加载 - 当请求URL等于
myvarUrl时,弹出提示框,同时onShouldStartLoadWithRequest返回false
实际运行时出现异常:弹出提示框后,如果用户点击「确定」,可以正常导航跳转回应用登录页;但如果用户点击「取消」留在当前页面,WebView就再也无法加载页面内包含的其他URL。核心表现为:对某一个URL请求返回false之后,onShouldStartLoadWithRequest就不再响应加载任何其他URL。
原问题代码
function verifyurl(url) { if (url == myvarUrl) { alerta(); return false; } else { setNewUrl(url) return true; } } async function alerta() { const response = await alert(); response && logOut(); } //Verifica se existe item na pilha navegação e retira function logOut() { let canGoBack = navigation.canGoBack(); canGoBack ? navigation.goBack() : console.log('AGIU MAIS DE UMA VEZ'); } /* ========================> Retorno de Visualização <======================== */ const Spinner = () => ( <View style={genericStyles.activityContainer}> <ActivityIndicator size="large" color="#f29900" /> </View> ); return ( <View style={genericStyles.containerWebView}> <StatusBar backgroundColor={genericStyles.colors.BlueBg} barStyle={'light-content'} /> <WebView source={{ uri: propriedade }} ref={webViewRef} originWhitelist={['*']} allowsInlineMediaPlayback javaScriptEnabled scalesPageToFit mediaPlaybackRequiresUserAction={false} javaScriptEnabledAndroid useWebkit startInLoadingState={true} cacheEnabled={false} renderLoading={Spinner} onShouldStartLoadWithRequest={(event) => { return verifyurl(event.url) }} /> </View> )
开发环境信息
react: ^17.0.2 => 17.0.2 react-native: ^0.68.2 => 0.68.2 react-native-webview: 11.22.2
问题原因
这个问题是react-native-webview iOS端(开启useWebkit时)的常见时序问题:
onShouldStartLoadWithRequest是原生端触发的同步回调,必须在同步执行流中立刻返回布尔值,一旦在回调执行过程中触发弹窗这类异步/阻塞UI线程的操作,会打乱WebView原生层的导航状态锁,导致回调被永久挂起,后续所有URL请求都不会再触发该回调。- 原代码在非拦截分支中直接调用
setNewUrl触发组件重渲染,会导致WebView的回调引用频繁变化,也会加大状态异常的概率。
修复方案
按照以下逻辑调整代码即可解决:
- 不要在
onShouldStartLoadWithRequest的同步执行流中直接触发弹窗、state更新这类会打断同步执行的操作,命中拦截规则时先同步返回false,把拦截到的URL存入临时state,等回调执行完成后,再通过监听state变化触发弹窗。 - 用户点击取消留在当前页时,主动调用WebView实例的
stopLoading()方法重置原生层的加载状态,清空拦截标记。 - 不需要触发UI更新的临时变量尽量用
useRef存储,避免不必要的组件重渲染影响WebView状态。
修复后核心代码示例
// 新增拦截状态标记、URL存储ref const [blockedUrl, setBlockedUrl] = useState(null); const newUrlRef = useRef(''); function verifyurl(url) { if (url === myvarUrl) { // 同步返回false阻止加载,仅做拦截标记,不触发任何异步/重渲染操作 setBlockedUrl(url); return false; } // 用ref存URL,不触发重渲染 newUrlRef.current = url; return true; } // 监听拦截标记,回调执行完成后再触发弹窗 useEffect(() => { if (!blockedUrl) return; // 建议使用RN官方Alert组件,不要用自定义阻塞式alert Alert.alert( '提示', '确定要返回登录页吗?', [ { text: '取消', style: 'cancel', onPress: () => { setBlockedUrl(null); // 重置WebView加载状态,解除导航锁 webViewRef.current?.stopLoading(); } }, { text: '确定', onPress: () => { setBlockedUrl(null); logOut(); } } ] ) }, [blockedUrl]) // logOut、Spinner、WebView渲染部分逻辑保持原有即可,注意把原来setNewUrl的相关逻辑替换为读取newUrlRef.current
如果调整后仍偶发锁死问题,可以在取消按钮的回调里额外加一句webViewRef.current?.reload()重载当前页面,即可完全恢复WebView的导航响应能力。
内容的提问来源于stack exchange,提问作者Hérick Raposo
相关产品推荐
相关产品推荐

