前言
我们为什么会用到webview?因为我们当时的场景是需要做一个活动,为了更新活动不用每次都要更新app,所以我们决定使用H5做活动,然后把活动嵌套在RN的页面中。这样我们每次更新活动不需要更新我们的app,然后活动可以随便定制,同时也提供个性化服务。也降低开发人员的开发成本。
安装依赖、链接原生依赖项
- 首先我们安装react-native-webview这个依赖包。
css
npm install --save react-native-webview
- 然后需要链接原生依赖项
从 react-native 0.60 开始,自动链接将。
在ios/
目录中运行
pod install
对于之前的版本的就需要手动链接。
java
react-native link react-native-webview
为什么需要这个步骤?因为这样才可以让编译器知道将它们包含在应用程序中。
在安卓的运行环境需要注意:Android - react-native-webview 版本 <6:运行链接命令后,此模块不需要任何额外步骤
android/gradle.properties
Android - react-native-webview 版本 >=6.XX:请通过编辑并添加两行代码确保您的项目中启用了 AndroidX :
ini
android.useAndroidX=true
android.enableJetifier=true
这个原生模块链接的问题我当时也是搞了好久才搞清楚,原生的模块链接如何操作可以参考下面这两篇文章。
揭秘 React Native 模块链接第一部分:使用 Cocoapods 清理你的 iOS 设置 ☕
揭秘 React Native 模块链接第二部分:完美链接 Android 原生模块的途径⛰️
React native 与webview的H5通信
RN与H5的通信其实是通过webview作为中间通道实现的,RN发消息给webview,然后H5监听message就拿到了RN给H5的消息。
文档上有两种方式来实现
- React Native -> Web:
injectedJavaScript
prop - React Native -> Web:
injectJavaScript
method
在我们的项目中我使用第一种方式,这是一个在网页首次加载后立即运行的脚本。即使页面重新加载或离开,它也只运行一次。我们只是形成一个消息通道,所以没必要动态变化的去使用injectJavaScript
method这种方式。
首先我们先封装一个RN的组件,用来加载webview,并创建一个ref---webViewRef绑定在webview上。
ini
<View style={styles.mainWrap}>
{h5Url ? (
<WebView
style={[styles.webviewWrap]}
source={{uri: h5Url}}
renderError={renderError}
{...webViewProps}
/>
) : (
<Text allowFontScaling={false} style={styles.errorMsg}>无效的网页地址</Text>
)}
</View>
webViewProps:
yaml
const webViewProps = useMemo(() => ({
ref: webViewRef,
bounces: true,
scrollEnabled: true,
androidHardwareAccelerationDisabled: true,
useWebkit: true,
startInLoadingState: true,
domStorageEnabled: true,
injectedJavaScript: injectedJavascriptFunc,
onMessage: handleReceiveH5Message
}), [handleReceiveH5Message]);
injectedJavascriptFunc:注意这里是字符串,这一步是为了给H5页面注入可以与webview通信的方法,在H5页面调用这个方法可以给webview发送消息,然后我们的RN组件就可以拿到H5传递的信息。
ini
export const injectedJavascriptFunc = `(function() {
window.postMessage = function(data) {
window.ReactNativeWebView.postMessage(data);
};
})()`;
通过webViewRef调用postMessage给webview发送消息,这里传递的消息必须是字符串。
ini
const postMessageToH5 = useCallback(message => {
webViewRef.current && webViewRef.current.postMessage(JSON.stringify(message));
}, [webViewRef]);
H5如何接收到消息呢?
javascript
function handleCallback(evt) {
const message = evt.data; // 字符串类型
try {
const data = JSON.parse(message);
if (_.isFunction(bindFunctionRef.current)) {
bindFunctionRef.current(data);
}
}
catch (e) {
console.log('WebView message: 格式解析错误');
}
};
window.addEventListener('message', handleCallback);
document.addEventListener('message', handleCallback);
这里可以看到我们注册了两遍事件,原因就是document.addEventListener在iOS上不生效。具体看这篇文章:在React Native中使用WebView的全面指南
到这一步我们就实现了从RN--->webview--->H5的通信,我们在H5的页面上就能够得到来自RN的信息了。
webview的H5与React native通信
- Web -> React Native:
postMessage
方法和onMessage
属性
必须设置onMessage,否则该window.ReactNativeWebView.postMessage方法将不会被注入到H5页中,这里RN就可以通过onMessage注册的方法获取到H5传递过来的消息。
那么H5如何给webview发送消息,还记得我们上面注入的injectedJavascriptFunc的代码,它是在H5页面加载后就执行的的脚本,也就是说只要H5页面加载了就执行了这段代码。
javascript
(function() {
window.postMessage = function(data) {
window.ReactNativeWebView.postMessage(data);
};
})()
那么我们相当于重写了window的postMessage方法,使其能够正确地与 React Native 的 WebView 通信。为什么这么做?
- 原理:React Native 的 WebView 组件和 H5 页面通信时,H5 端需要通过 window.ReactNativeWebView.postMessage(data) 向 RN 发送消息。但有些 H5 页面可能只会调用标准的window.postMessage,这时消息不会被 RN 收到。
- 作用:这段注入的 JS 代码会把 H5 里的 window.postMessage 方法重写为调用 window.ReactNativeWebView.postMessage,保证无论 H5 调用哪个方法,消息都能被 RN 层收到。
- 使用场景:适用于 RN WebView 加载的第三方或自有 H5 页面,确保消息通信兼容性。
那么在我们的H5页面上就可以调用这个方法给webview发送消息。
javascript
export default () => {
// 发送消息至RN
const postMessage = useCallback((eventName, data) => {
console.log('发送到RN的数据为:', eventName, data);
if (window.ReactNativeWebView) {
const requestBody = JSON.stringify({
type: eventName,
data
});
window.ReactNativeWebView.postMessage(requestBody);
} else {
const requestBody = JSON.stringify({
type: eventName,
data
});
window.postMessage(requestBody);
}
}, []);
return {
postMessage
};
};
然后webview通过我们注册的回调函数onMessage就得到了来自H5的信息。
vbnet
// RN接收h5的消息
const handleReceiveH5Message = useCallback(async e => {
const dataStr = e.nativeEvent.data;
if (dataStr && dataStr.toString() !== 'undefined') {
try {
}
catch (error) {
throw error;
}
}
}, []);