修复iOS的React Native WebView的postMessage。

2016年,GitHub用户Robert Roskam(raiderrobert)在React Native存储库上打开了一个问题,报告错误“在WebView上设置onMessage会覆盖window.postMessage的现有值,但定义了先前的值” 。 从那以后的两年中,在WebView的内部React Native实现中,没有任何事情可以解决它。

React Native社区专门分叉了WebView,以将其作为第三方软件包进行维护,并修复了许多此类持续存在的问题。 但是,为了实现这些第三方程序包,您必须能够链接React Native程序包— react-native link react-native-webview 。 如果您有能力并且愿意这样做,那么您的问题就可以解决。 WebView社区版的安装说明很简单:

 yarn add https://github.com/react-native-community/react-native-webview 
react-native link react-native-webview

注意:为了使react-native link ... ,您必须首先对yarn global add react-native

不幸的是,如果您不能或不愿意这样做,则根本无法解决此问题。 多年!

例如,Expo的用户将不得不退出他们的项目并编写自己的本机非JavaScript实现的功能。 从理论上讲,世博会将在将来的发行版中使用这些社区版软件包。 但是距离发射窗口只有几周的路程,我的团队和我自己都不愿意等待。

解决方案💡

如果您比现在更关心如何解决此问题,那么本节适合您。

npm install rn-webview --saveyarn add rn-webviewrn-webview包添加到您的项目中。

无论您在何处使用import { WebView } from 'react-native' ,只需将其替换为import WebView from 'rn-webview' 。 然后只需像使用React Native内部实现一样使用新的WebView组件,包括使用onMessage属性。 rn-webview包只是内部React Native实现的包装,它通过与内部onMessage道具不同的通道截取消息,但使用自己的onMessage道具处理它,给人一种幻觉,即您实际上正在按预期使用内部onMessage结果。

注意事项🤕

rn-webview包的工作方式是将window.postMessage通信定向到history.pushState 。 尽管React Native的iOS实现无法正确处理window.postMessage ,但它可以处理导航状态更改。 因此,导航状态更改事件是WebView与本机应用程序之间通过其传输消息的渠道。

如果操作历史状态是应用程序的重要方面,则此解决方案可能不适合您的需求。 随时在GitHub上分叉该项目以提供替代解决方案。 欢迎提出要求和问题!

实施🔨

出口🚢

首先,WebView的ref道具特别重要。 因此,我们不希望用户失去对它的访问权限。 我们从一个forwardRef实现开始该程序包,其中WebViewPostMessage是用于此程序包的类名。

渲染🎨

该组件的输出将是WebView的React Native内部实现,需要进行一些调整。 我们不会给它提供forwardedRef属性,因为它仅用于授予父级对ref访问权限,而对于内部WebView则完全没有意义。 最重要的是,我们不会提供onMessage道具,因为这是我们所有问题的根源onMessage不支持它!

我们有一个自定义的导航状态更改侦听器,因为这是我们用来侦听消息的渠道。

我们有一个自定义的ref处理程序,因为我们两者都需要1)需要在该组件内部对其进行访问,并且2)都需要通过forwardedRef属性将ref传递回父容器。

参考👋

当内部WebView给我们提供其引用时,我们将其存储在实例( this.ref = ref )上以备后用。 如果父母也要求提供参考,我们将其转发。

注入window.postMessage

现在,需要在WebView的任何页面上存在window.postMessage的自定义实现。 每当导航状态更改时,如果它已完成加载,我们就会向其中注入JavaScript以覆盖window.postMessage所做的操作。

为了便于阅读,我从另一个文件定义并导入了injectPostMessage

它是立即调用的函数表达式,以确保我们的变量均不与网页冲突。

EMPTY_STATE是推送到历史记录的内容,因为我们不会在事件监听器中使用状态对象。

escape函数对字符串中的撇号进行转义,以便我们可以将该字符串置于撇号中。 由于我们推送的导航状态不是真正的JavaScript,并且不会通过任何类型的JavaScript解释器传递,因此此步骤并非完全必要。 它只允许我们推动更紧密地模仿真实JavaScript的状态。

postMessage变量检查是否已存在postMessage函数。 如果是这样,我们还要在任何window.postMessage调用期间执行它。

我们定义了自己的window.postMessage函数。 它所做的第一件事是执行先前的window.postMessage函数(如果存在)。

接下来,我们进入历史状态。 我们没有状态对象,因此我们使用前面提到的空对象。 该文档的标题没有更改,因此我们只使用当前的标题。 文档的位置本身也没有改变:我们只是附加一个哈希。

我们稍后将要监听的哈希是window.postMessage('the message') 。 从设计上看,它看起来像JavaScript,但是不会被任何真正的JavaScript解释器评估。 我们只需要一个不会与真实的文档内哈希冲突的唯一哈希即可。

postMessage侦听器📬

现在我们有了自己的window.postMessage事件发射器,我们需要侦听它。 这是handleNavigationStateChange方法顶部的代码。

我们检查新URL是否与我们先前定义的postMessage哈希匹配。 如果是这样,我们将return以便不会触发其余的导航状态更改事件侦听器。 这是一个消息事件,而不是导航状态更改(不包括技术)。

每个postMessage事件将触发两次导航状态更改-一次用于loading: true ,一次,几乎紧随其后,用于loading: false 。 我们只在监听loading: true事件,因为它首先发生。 loading: false事件将被忽略,因为它只是重复项。

仅当父组件传递了onMessage事件处理程序时,我们才使用包含消息的模拟事件调用该处理程序。 我们在传递消息之前先对其进行转义,因为我们更早地转义了撇号。

unescape函数在文档顶部定义,因为它是常量(不依赖于实例),并且不必是组件的方法。 如果您希望对代码进行拆分,则可以导入它。

onNavigationStateChange🕵

上面介绍了拦截window.postMessage并使用自己的onMessage事件侦听器进行处理所需的一切。 我们原来的问题已经解决onMessage可以与此WebView一起使用。 但是,由于我们已覆盖内部的onNavigationStateChange侦听器,因此父级不再接收导航状态更改事件。

handleNavigationStateChange事件侦听器的底部,添加以下内容:

如果父级包含一个onNavigationStateChange道具,请对其进行调用,并为其提供此导航状态更改事件。

空返回仅仅是个人喜好-我不认为函数应该有条件地返回,即使它在功能上等同于隐式返回。

结论🔚

提醒一下,您可以通过安装NPM的rn-webview软件包来包括刚刚概述的组件。 您也可以在GitHub上进行分叉。 欢迎提出问题和请求。

如果您喜欢这篇文章,请随意拍一两下。 快速,简单,免费! 如果您有任何疑问或相关评论,请保留在下面的评论中。

要阅读更多我的专栏,您可以在LinkedIn和Twitter上关注我,或者在CharlesStover.com上查看我的投资组合。