在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按钮,即可打开控制台。
相关推荐
liangshanbo121537 分钟前
创建可重用React组件的实用指南
前端·javascript·react.js
Dragon Wu5 小时前
前端框架 react 性能优化
前端·javascript·react.js·性能优化·前端框架·react
PleaSure乐事8 小时前
JS/JSP/JSX的区别与关联
前端·javascript·react.js·前端框架·jsp·jsx
GISer_Jing17 小时前
从0开始分享一个React项目:React-ant-admin
前端·react.js·前端框架
刺客-Andy20 小时前
React第六节 组件属性prop的propTypes类型使用介绍
前端·javascript·react.js·typescript
刺客-Andy1 天前
React第四节 组件的三大属性之state
前端·javascript·react.js
黄毛火烧雪下1 天前
React 表单Form 中的 useWatch
前端·javascript·react.js
独上归州1 天前
Vue与React的Suspense组件对比
前端·vue.js·react.js·suspense
秦时明月之君临天下1 天前
React和Next.js的相关内容
前端·javascript·react.js
米奇妙妙wuu1 天前
React中 setState 是同步的还是异步的?调和阶段 setState 干了什么?
前端·javascript·react.js