在React Native中使用WebView的全面指南

在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目录,打开扩展名为xcworkspacexcodeproj的文件,然后在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上,并通过调用postMessagewebview发送数据

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>

可以看到,分别用documentwindow注册了两遍事件,这其实因为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

  1. 首先打开safari的偏好设置,在高级面板中勾上在菜单栏中显示"开发"菜单
  2. 运行iOS模拟器,并进入WebView页面。
  3. 切换到safari,此时可以在开发菜单中选择你的模拟设备,在展开菜单中选择document就能打开控制台了。

4.2 使用chrome调试android的webview

  1. 运行安卓模拟器,并进入webview页面
  2. 在chrome地址栏中输入并访问chrome://inspect/#devices
  3. 在页面中点击inspect按钮,即可打开控制台。
相关推荐
wen's8 小时前
React Native 0.79.4 中 [RCTView setColor:] 崩溃问题完整解决方案
javascript·react native·react.js
Jackson_Mseven14 小时前
面试官:useEffect 为什么总背刺?我:闭包、ref 和依赖数组的三角恋
前端·react.js·面试
前端小盆友17 小时前
从零实现一个GPT 【React + Express】--- 【2】实现对话流和停止生成
前端·gpt·react.js
OLong18 小时前
2025年最强React插件,支持大量快捷操作
前端·react.js·visual studio code
朝阳3918 小时前
ReactNative【实战系列教程】我的小红书 3 -- 自定义底栏Tab导航(含图片选择 expo-image-picker 的使用)
react native
摸鱼仙人~18 小时前
重塑智能体决策路径:深入理解 ReAct 框架
前端·react.js·前端框架
namehu18 小时前
浏览器中的扫码枪:从需求到踩坑再到优雅解决
前端·react.js
杨进军18 小时前
React 使用 MessageChannel 实现异步更新
react.js
namehu18 小时前
浏览器中的打印魔法:Lodop与系统打印机
前端·react.js
如果'\'真能转义说18 小时前
React自学 基础一
前端·react.js·前端框架