在React Native中使用WebView的全面指南
React Native让我们可以用jsx/tsx和React,来开发iOS/安卓应用。但有时我们仍然需要一个浏览器环境,比如某些node包需要DOM或者我们有一个现成的html需要嵌入app中等等......
react-native-webview
为了达成以上需求,我们使用react-native-webview。在较新版本的React Native中不再默认包含webview
组件,我们需要自己安装。
1. 安装
bash
yarn add react-native-webview
npm install --save react-native-webview
iOS需要安装pod,进入ios
目录运行以下命令即可
bash
pod install
2. 使用
react-native-webview的使用非常简洁,就像使用任何一个react组件一样。
2.1 调用远程页面
jsx
import React from 'react'
import { WebView } from 'react-native-webview'
const App = () => {
return (
<WebView
source={{
uri: 'https://www.juejin.cn'
}}
style={{
flex: 1
}}
/>
)
}
export default App
2.2 调用本地页面
调用本地页面可以让我们直接更自由的使用webview,你甚至可以使用各种框架编写spa页面,然后将打包产物嵌入webview,完成
native
->rn
->react/vue
->html
究极套娃......扯远了
首先我们在根目录创建一个新的文件夹public
(任何名字都可以),然后创建一个Web.bundle
文件夹,这样xcode
会识别该文件夹为一个包。然后放入我们的index.html
。
html
<html lang="en">
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<p>Hello,React Native WebView</p>
</body>
<style>
body {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
</style>
</html>
android配置
打开根目录/android/app/build.gradle
,添加以下内容
java
android {
...
sourceSets {
main {
assets.srcDirs = ['src/main/assets','../../public']
}
}
}
iOS配置
进入ios
目录,打开扩展名为xcworkspace
或xcodeproj
的文件,然后在xcode
中,拖入Web.bundle
文件夹。
xcode
会弹出提示,参照图中选项。由此准备工作就完成了,我们重新编译打包下。
组件中使用
现在我们回到rn组件,更新uri
配置。注意加载路径的平台差别。originWhitelist
用于解决跨域问题
jsx
import React from 'react'
import { SafeAreaView, Platform } from 'react-native'
import { WebView } from 'react-native-webview'
const App = () => {
return (
<SafeAreaView
style={{
flex: 1,
backgroundColor: '#fff',
}}
>
<WebView
source={{
uri: `${
Platform.OS === 'android' ? 'file:///android_asset/' : ''
}Web.bundle/index.html`,
}}
originWhitelist={['*']}
style={{
flex: 1,
}}
/>
</SafeAreaView>
)
}
export default App
3. React Native与WebView通信
只显示内容显然不够用,我们经常有在RN与WebView间通信的需求。
先给我们的index.html
添加一个节点
html
<body>
......
<p id="run-first">empty</p>
</body>
3.1 React Native => WebView
1. injectedJavaScript
injectedJavaScript
可以在webview加载完页面后执行js,注意传入的是字符串。在iOS上还必须加上onMessage
属性,否则不能触发。重新build之后,就可以看到效果了。类似的,还有一个injectedJavaScriptBeforeContentLoaded
属性,不同点是这个属性的方法会在页面加载前触发。
tsx
import { SafeAreaView, Platform } from 'react-native'
import WebView from 'react-native-webview'
const App = () => {
const runFirst = `const el = document.getElementById('run-first');
el.innerText = 'run first!'`
return (
<SafeAreaView
style={{
flex: 1,
backgroundColor: '#fff',
}}
>
<WebView
source={{
uri: `${
Platform.OS === 'android' ? 'file:///android_asset/' : ''
}Web.bundle/index_.html`,
}}
originWhitelist={['*']}
style={{
flex: 1,
}}
injectedJavaScript={runFirst}
onMessage={() => {}}
/>
</SafeAreaView>
)
}
export default App
2. postMessage
postMessage
可以让我们手动触发发送数据到webview
,注意发送的数据必须为字符串类型,在webview
中通过data
字段接收。
在rn组件中创建refWebView
绑定到WebView
上,并通过调用postMessage
向webview
发送数据
tsx
import { SafeAreaView, Platform, View, Pressable, Text } from 'react-native'
import WebView from 'react-native-webview'
import { useRef } from 'react'
const App = () => {
const runFirst = `const el = document.getElementById('run-first');
el.innerText = 'run first!'`
const refWebView = useRef<WebView | null>(null)
const onPress = () => {
refWebView.current.postMessage('message from react native')
}
return (
<SafeAreaView
style={{
flex: 1,
backgroundColor: '#fff',
}}
>
<View>
<Pressable onPress={onPress}>
<Text>Post Message</Text>
</Pressable>
</View>
<WebView
ref={refWebView}
source={{
uri: `${
Platform.OS === 'android' ? 'file:///android_asset/' : ''
}Web.bundle/index_.html`,
}}
originWhitelist={['*']}
style={{
flex: 1,
}}
injectedJavaScript={runFirst}
onMessage={() => {}}
/>
</SafeAreaView>
)
}
index.html
html
<script>
document.addEventListener('message', (msg) => {
const el = document.getElementById('run-first')
el.innerText = msg.data
})
window.addEventListener('message', (msg) => {
const el = document.getElementById('run-first')
el.innerText = msg.data
})
</script>
可以看到,分别用document
和window
注册了两遍事件,这其实因为document.addEventListener
在iOS平台无效。查看源码/apple /RNCWebViewImpl.m
Objective-C++
- (void)postMessage:(NSString *)message
{
NSDictionary *eventInitDict = @{@"data": message};
NSString *source = [
NSString stringWithFormat:@"window.dispatchEvent(new MessageEvent('message', %@));",
RCTJSONStringify(eventInitDict, NULL)
];
[self injectJavaScript: source];
}
可以看到,使用了window.dispatchEvent
来派发事件,所以为了兼容iOS需要用window.addEventListener
来监听事件。现在,点击post Message
按钮,就可以看到我们发送的消息出现在webview中了!
3.2 WebView => React Native
从WebView发送数据到RN,使用window.ReactNativeWebView.postMessage
。同样的,只能发送字符串类型,rn组件中使用onMessage
来接收。
index.html
中新增一个send message
按钮,绑定sendMessage
方法。
html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<p id="run-first">empty</p>
<p>Hello,React Native WebView</p>
<button onclick="sendMessage()">send Message</button>
</body>
<style>
body {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
</style>
<script>
const sendMessage = () => {
window.ReactNativeWebView.postMessage('message from webview')
}
document.addEventListener('message', (msg) => {
const el = document.getElementById('run-first')
el.innerText = msg.data
})
window.addEventListener('message', (msg) => {
const el = document.getElementById('run-first')
el.innerText = msg.data
})
</script>
</html>
在rn组件中,使用onMessage监听,并通过设置状态直接反映到屏幕上。
tsx
import { SafeAreaView, Platform, View, Pressable, Text } from 'react-native'
import WebView from 'react-native-webview'
import { useRef, useState } from 'react'
const App = () => {
const runFirst = `const el = document.getElementById('run-first');
el.innerText = 'run first!'`
const refWebView = useRef<WebView | null>(null)
const onPress = () => {
refWebView.current.postMessage('message from react native')
}
const [msgFromWV, setMsgFromWV] = useState('')
return (
<SafeAreaView
style={{
flex: 1,
backgroundColor: '#fff',
}}
>
<View>
<Pressable onPress={onPress}>
<Text>Post Message</Text>
</Pressable>
<Text>{msgFromWV}</Text>
</View>
<WebView
ref={refWebView}
source={{
uri: `${
Platform.OS === 'android' ? 'file:///android_asset/' : ''
}Web.bundle/index_.html`,
}}
originWhitelist={['*']}
style={{
flex: 1,
}}
injectedJavaScript={runFirst}
onMessage={event => {
setMsgFromWV(event.nativeEvent.data)
}}
/>
</SafeAreaView>
)
}
export default App
如图,我们从webview发送的数据正常地反映出来了!
4. 调试
最后来讲讲如何调试webview,毕竟前端程序员要是没有console.log
基本和瞎了差不多w
4.1 使用safari
调试iOS
的webview
- 首先打开safari的
偏好设置
,在高级
面板中勾上在菜单栏中显示"开发"菜单
- 运行iOS模拟器,并进入WebView页面。
- 切换到
safari
,此时可以在开发
菜单中选择你的模拟设备,在展开菜单中选择document
就能打开控制台了。
4.2 使用chrome
调试android
的webview
- 运行安卓模拟器,并进入webview页面
- 在chrome地址栏中输入并访问
chrome://inspect/#devices
- 在页面中点击
inspect
按钮,即可打开控制台。