React-Native中如何无需重渲染即可向iframe发送postMessage消息
解决方案
核心问题原因
- 你当前代码无法调用postMessage有三个显性bug,还有1个RN Web环境特性问题:
handleInjectedJavaScript方法没有返回处理后的HTML字符串,导致iframe的srcDoc属性为undefined,iframe实际没有加载你传入的页面- 注入iframe的message监听代码里,你直接读取
message变量,实际事件触发后传入的参数是event,消息内容存在event.data中 - 没有等待iframe加载完成就调用
injectJavaScript,此时contentWindow还未初始化 - 部分版本React Native Web对原生DOM元素的ref封装需要通过额外属性获取真实DOM节点,大部分版本直接取
iframeRef.current即可拿到原生iframe DOM
修复后的代码
组件代码
import * as React from 'react'; interface SourceHTML { html: string; } type IFrameProps = { source: SourceHTML, } export interface Ref { injectJavaScript?: (value: string) => void, } const IFrameComponent = React.forwardRef<Ref, IFrameProps>((props, ref) => { const iframeRef = React.useRef<HTMLIFrameElement | null>(null); // 用ref存加载状态,不触发重渲染 const isIframeLoaded = React.useRef(false); const { source } = props; React.useImperativeHandle(ref, () => ({ injectJavaScript: (message: string) => { if (!iframeRef.current || !isIframeLoaded.current) return; if(message.toString().includes("true")) { const pieces: Array<string> = message.toString().split(";")[0].trim().split("(") const stringFunction = pieces[0].trim() const parameters = pieces[1].replace(")", "") const messageObj = { action: "evaluateFunction", stringFunction: stringFunction, parameters: parameters, } // 直接拿原生DOM的contentWindow发消息 iframeRef.current.contentWindow?.postMessage(messageObj, '*'); } } })) const handleInjectedJavaScript = (html: string) => { if (!html) return html; const javascript = ` function evaluateFunction(stringFunction, parameters) { window[stringFunction](parameters) } window.addEventListener("message", (event) => { // 修复:从event.data取消息内容 const message = event.data; if(typeof message === 'object' && message.action === "evaluateFunction") { const {stringFunction, parameters} = message; evaluateFunction(stringFunction, parameters); } }) ` if(!html.includes(javascript)) { html = html.replace('</body>', `<script>${javascript}</script></body>`); } // 修复:返回处理后的html return html; } const handleIframeLoad = () => { isIframeLoaded.current = true; } return ( React.createElement( 'iframe', { ref: iframeRef, id: "IFrame", srcDoc: handleInjectedJavaScript(source.html), frameBorder: '0', seamless: true, // 新增load事件监听,确认加载完成 onLoad: handleIframeLoad } ) ) }) IFrameComponent.displayName = 'IFrame'; export {IFrameComponent} export default IFrameComponent;
调用注意事项
调用injectJavaScript需要确保组件已经挂载完成,可以放在useEffect中执行,避免ref为undefined:
import React, { useEffect, useRef } from 'react'; import IFrameComponent from './IFrameComponent'; const App = () => { const webViewRef = useRef<{injectJavaScript?: (value: string) => void}>(null); const html = ` <html> <body> <script> function AlertMessage(message) { alert(message); } </script> </body> </html> `; useEffect(() => { // 确保组件挂载后再调用 webViewRef.current?.injectJavaScript("AlertMessage('Hello');true;"); }, []) return <IFrameComponent ref={webViewRef} source={{ html: html }} /> } export default App;
兼容说明
- 如果你使用的React Native Web版本无法通过
iframeRef.current直接拿到原生DOM,可以尝试读取iframeRef.current?.node或者iframeRef.current?.domNode属性获取真实iframe节点 - 该方案所有状态都存储在
useRef中,没有触发组件重渲染的逻辑,完全符合你不触发重渲染的要求
内容的提问来源于stack exchange,提问作者DarkComet
相关产品推荐
相关产品推荐

