likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 20

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

React Native让我们可以用jsx/tsx和React,来开发iOS/安卓应用。但有时我们仍然需要一个浏览器环境,比如某些node包需要DOM或者我们有一个现成的html需要嵌入app中等等……

react-native-webview

为了达成以上需求,我们使用react-native-webview。在较新版本的React Native中不再默认包含webview组件,我们需要自己安装。

1. 安装

yarn add react-native-webview

npm install --save react-native-webview

iOS需要安装pod,进入ios目录运行以下命令即可

pod install

2. 使用

react-native-webview的使用非常简洁,就像使用任何一个react组件一样。

2.1 调用远程页面

2.2 调用本地页面

调用本地页面可以让我们直接更自由的使用webview,你甚至可以使用各种框架编写spa页面,然后将打包产物嵌入webview,完成native->rn->react/vue->html究极套娃……扯远了

首先我们在根目录创建一个新的文件夹public(任何名字都可以),然后创建一个Web.bundle文件夹,这样xcode会识别该文件夹为一个包。然后放入我们的index.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,添加以下内容

android {

  sourceSets {
    main {
      assets.srcDirs = ['src/main/assets','../../public']
    }
  }
}

iOS配置

进入ios目录,打开扩展名为xcworkspacexcodeproj的文件,然后在xcode中,拖入Web.bundle文件夹。

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

xcode会弹出提示,参照图中选项。由此准备工作就完成了,我们重新编译打包下。

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

组件中使用

现在我们回到rn组件,更新uri配置。注意加载路径的平台差别。originWhitelist用于解决跨域问题

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添加一个节点

<body>
  ……
  <p id="run-first">empty</p>
</body>

3.1 React Native => WebView

1. injectedJavaScript

injectedJavaScript可以在webview加载完页面后执行js,注意传入的是字符串。在iOS上还必须加上onMessage属性,否则不能触发。重新build之后,就可以看到效果了。类似的,还有一个injectedJavaScriptBeforeContentLoaded属性,不同点是这个属性的方法会在页面加载前触发。

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发送数据

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

<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

- (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 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监听,并通过设置状态直接反映到屏幕上。

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就能打开控制台了。

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

4.2 使用chrome调试android的webview

  1. 运行安卓模拟器,并进入webview页面
  2. 在chrome地址栏中输入并访问chrome://inspect/#devices
  3. 在页面中点击inspect按钮,即可打开控制台。

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