2019-04-03
使用WebView组件可以显示一个HTML网页,这里记录一下react-native如何跟HTML页面进行通信,即实现双向的事件调用和数据传递。
一、React-Native调用HTML页面事件
1、在HTML页面添加对RN事件的监听,。
window.onload = function(){
addReactNativeListener();
};
/**
* document.addEventListener("message",function(event) {}
* 监听RN发送的事件,通过event.data获取传递过来的参数值
*/
function addReactNativeListener() {
document.addEventListener("message",function(event) {
document.getElementById("showInfoElement").innerHTML = "RN发送事件到HTML页面,传递过来的参数:" + event.data;
});
}
2、RN端发送事件,我们通过一个按钮事件来执行这个操作。
<Button title={'RN发送事件到HTML页面'} onPress={() => {
this.refs.webView.postMessage("RN发送事件到HTML页面");
}}/>
postMessage()只能传递一个字符串参数,通常会使用JSON字符串,携带操作标识和相关参数,以完成多种业务操作。这里是简单的传递了一个字符串。
3、IOS端效果
二、HTML页面调用React-Native事件
1、编写一段HTML代码,WebView加载显示本地的HTML。
const HTML = `
<!DOCTYPE html>
<html lang="en">
<head>
<title>WebView组件与HTML页面通信机制实践</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div>
<button onclick="sendEventToReactNative()" >发送事件到React-Native端</button>
</div>
<p>React-Native WebView组件与HTML页面通信机制实践</p>
<div id="showInfoElement"></div>
</body>
<script>
window.onload = function(){
addReactNativeListener();
};
/**
* document.addEventListener("message",function(event) {}
* 监听RN发送的事件,通过event.data获取传递过来的参数值
*/
function addReactNativeListener() {
document.addEventListener("message",function(event) {
document.getElementById("showInfoElement").innerHTML = "RN发送事件到HTML页面,传递过来的参数:" + event.data;
});
}
function sendEventToReactNative(){
console.log('sendEventToReactNative');
document.getElementById("showInfoElement").innerHTML = "发送事件到React-Native端";
let params = {};
params.type = 'eventName';
params.message = "这是HTML页面传递到React-Native端的参数值";
if(window.postMessage) {
window.postMessage(JSON.stringify({type: 'eventName',message:"这是HTML页面传递到React-Native端的参数值"}));
}
}
</script>
</html>
`;
2、WebView组件通过onMessage(event)处理HTML页面回调事件,通常我们在HTML页面会发送一个JSON字符串到RN端,JSON里面携带操作标识字段和相关的参数字段,因为window.postMessage()方法只能携带一个字符串参数,所以我们把参数封装到JSON字符串里面。RN端拿到这个JOSN字符串后,再把它解析成JSON对象,就能通过操作标识字段来执行相应的业务操作了。(HTML发送的JSON字符串会存放到event.nativeEvent.data这个属性)
/**
* 在 webview 内部的网页中调用 window.postMessage 方法时可以触发此属性对应的函数(onMessage),从而实现网页和 RN 之间的数据交换。
* 设置此属性的同时会在 webview 中注入一个 postMessage 的全局函数并覆盖可能已经存在的同名实现。
*
* 网页端的 window.postMessage 只发送一个参数 data,此参数封装在 RN 端的 event 对象中,即 event.nativeEvent.data。data 只能是一个字符串。
*
* @param event
* @private
*/
_handleMessage(event) {
try {
//将HTML页面传递过来的数据转化成JSON对象
const objData = JSON.parse(event.nativeEvent.data);
console.log('');
console.log('_handleMessage');
console.log(objData);
switch (objData.type) {
case 'eventName': //测试html与rn的交互
alert(objData.message);
break;
}
} catch (error) {
console.log('_handleMessage catch');
console.log(error);
}
}
3、在HTML页面当中,利用window.postMessage()方法给RN端发送事件,当HTML发送事件后,就会回调WebView组件的onMessage(event)事件,这样就实现HTML页面与RN之间的数据传递了。上述代码就是点击按钮的时候,往RN发送一个事件。
下面是React-Native官方文档对onMessage回调事件的说明:
在 webview 内部的网页中调用 window.postMessage 方法时可以触发此属性对应的函数,从而实现网页和 RN 之间的数据交换。 设置此属性的同时会在 webview 中注入一个 postMessage 的全局函数并覆盖可能已经存在的同名实现。
网页端的 window.postMessage 只发送一个参数 data,此参数封装在 RN 端的 event 对象中,即 event.nativeEvent.data。data 只能是一个字符串。
类型 |
必填 |
function |
否 |
4、运行效果
三、全部代码
import React, {Component} from 'react';
import {ActivityIndicator, Dimensions, View, WebView, Platform, Button} from 'react-native';
const {width, height} = Dimensions.get('window');
const HTML = `
<!DOCTYPE html>
<html lang="en">
<head>
<title>WebView组件与HTML页面通信机制实践</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div>
<button onclick="sendEventToReactNative()" >发送事件到React-Native端</button>
</div>
<p>React-Native WebView组件与HTML页面通信机制实践</p>
<div id="showInfoElement"></div>
</body>
<script>
window.onload = function(){
addReactNativeListener();
};
/**
* document.addEventListener("message",function(event) {}
* 监听RN发送的事件,通过event.data获取传递过来的参数值
*/
function addReactNativeListener() {
document.addEventListener("message",function(event) {
document.getElementById("showInfoElement").innerHTML = "RN发送事件到HTML页面,传递过来的参数:" + event.data;
});
}
function sendEventToReactNative(){
console.log('sendEventToReactNative');
document.getElementById("showInfoElement").innerHTML = "发送事件到React-Native端";
let params = {};
params.type = 'eventName';
params.message = "这是HTML页面传递到React-Native端的参数值";
if(window.postMessage) {
window.postMessage(JSON.stringify({type: 'eventName',message:"这是HTML页面传递到React-Native端的参数值"}));
}
}
</script>
</html>
`;
export default class WebViewCommunicationExample extends React.Component {
static navigationOptions = {
headerTitle: 'WebView组件与HTML页面通信机制实践'
};
constructor(props) {
super(props);
this.props = props;
this.state = {height: 200};
}
/**
* 在 webview 内部的网页中调用 window.postMessage 方法时可以触发此属性对应的函数(onMessage),从而实现网页和 RN 之间的数据交换。
* 设置此属性的同时会在 webview 中注入一个 postMessage 的全局函数并覆盖可能已经存在的同名实现。
*
* 网页端的 window.postMessage 只发送一个参数 data,此参数封装在 RN 端的 event 对象中,即 event.nativeEvent.data。data 只能是一个字符串。
*
* @param event
* @private
*/
_handleMessage(event) {
try {
//将HTML页面传递过来的数据转化成JSON对象
const objData = JSON.parse(event.nativeEvent.data);
console.log('');
console.log('_handleMessage');
console.log(objData);
switch (objData.type) {
case 'eventName': //测试html与rn的交互
alert(objData.message);
break;
}
} catch (error) {
console.log('_handleMessage catch');
console.log(error);
}
}
/**
* SceneView里面嵌套WebView组件,WebView组件不允许滑动,需要动态设置WebView的高度为HTML内容的高度
* @returns {*}
*/
render() {
let webViewSource;
if (Platform.OS === 'android') {
webViewSource = {
html: HTML,
baseUrl: '' //即使没有baseUrl,也要加上这个属性,写上空串,解决android加载页面乱码的问题
}
} else if (Platform.OS === 'ios') {
webViewSource = {
html: HTML,
//baseUrl:'' //IOS不要加这个属性,会导致HTML加载不出来
}
}
return (
<View style={{flex: 1}}>
<Button title={'RN发送事件到HTML页面'} onPress={() => {
this.refs.webView.postMessage("RN发送事件到HTML页面");
}}/>
<WebView
style={{flex: 1, height: this.state.height}}
// source={{
// html: HTML,
// //baseUrl:'' //即使没有baseUrl,也要加上这个属性,写上空串,解决android加载页面乱码的问题
// }}
source={webViewSource}
ref={'webView'}
dataDetectorTypes={'none'}
automaticallyAdjustContentInsets={false}
startInLoadingState={true}
renderLoading={() => {
return (<View
style={{
flex: 1,
height: 200,
backgroundColor: "#fff",
justifyContent: "center",
alignItems: "center"
}}>
<ActivityIndicator color={"#000"}/>
</View>);
}}
allowFileAccessFromFileURLs={true}
domStorageEnabled={true}
scrollEnabled={false}
javaScriptEnabled={true}
showsVerticalScrollIndicator={true}
showsHorizontalScrollIndicator={false}
onMessage={(event) => {
this._handleMessage(event)
}}
// 初始化webview注入全局代码
injectedJavaScript={BaseScript}
/>
</View>
);
}
};
const BaseScript =
`
window.BTAppBridge = {
initAttachment:function(data){
var obj = {};
obj.type = 'attachment';
try{
obj.dataList = data?JSON.parse(data):[];
}catch(e){
obj.dataList = [];
}
setTimeout(function(){
window.postMessage(JSON.stringify(obj));
},200);
}
}
(function () {
var height = null;
function changeHeight() {
h = document.body.scrollHeight;
if (h != height) {
height = h;
if (window.postMessage) {
window.postMessage(JSON.stringify({type: 'setHeight',height: height}));
}
}
}
setInterval(changeHeight, 500);
} ())
`;
四、遇到的问题
1、在Android端,WebView加载本地HTML代码后乱码,在source里面加入baseUrl这个属性。
webViewSource = {
html: HTML,
baseUrl: '' //即使没有baseUrl,也要加上这个属性,写上空串,解决android加载页面乱码的问题
}
2、在部分Android模拟器上,HTML页面执行window.postMessage()方法没有反应,暂时找不到解决方案。在真机上运行是可以的。