父页面向iframe子页面传值的3种方式

1,818 阅读4分钟

场景:父页面中定义iframe标签,通过更改src嵌入不同的页面,每次src改变传入不同的值到iframe。使用语言是react

向iframe子页面中传值有三种方式:

一、使用postMessage传递数据【适用于同源和非同源】

【问题】更改src时会出现数据传递失效问题。

【原因】onload 事件在frame或者iframe载入完成后被触发,iframe载入完成(onload事件触发)与iframe页面渲染完成并不完全是一回事,它们分别标志着不同的加载阶段:

  1. iframe载入完成 (onload事件) :

    • iframe内部的文档(即src指向的HTML页面)已经从服务器下载完毕,并且其中的所有外部资源(如图片、样式表、脚本等)也已经加载完成时,iframeonload事件会被触发。这意味着iframe的内容已经存在于浏览器中,但不一定表示用户能看到最终的渲染结果,因为脚本执行、CSS渲染等可能还在进行中。
  2. iframe页面渲染完成:

    • 这通常指的是iframe内部的页面不仅加载完毕,而且所有的JavaScript执行结束、CSS布局和绘制完成,最终的视觉呈现已经稳定下来,用户可以看到完整的、经过样式渲染和动态处理后的页面内容。这个时间点可能在onload事件之后,特别是当页面上有复杂的JavaScript逻辑、异步加载内容或是动画效果时。

简单来说,onload事件标志着基本的资源加载完成,而页面渲染完成则是指这些资源已经被处理并展示出来,用户界面达到最终状态。在某些高性能要求的应用场景中,可能还需要监听其他事件(如DOMContentLoaded事件或自定义的渲染完成回调)来更精确地判断页面是否完全准备好。

所以onload在子页面还没有渲染时就开始发送数据了,此时子页面的监听还没有设置,错过了数据,所以没有监听到,可以通过子页面加载完成后通知父页面,父页面收到消息再传递参数的方式实现,具体代码实现如下

/** 父页面 */
// currentBreadcrumb是每次src变化时候要传入的变化的数据
const IframePage = ({ src, currentBreadcrumb }) => {
  const iframeRef = useRef();
  useEffect(() => {
    const handleMessage = () => {
      iframeRef.current.contentWindow.postMessage(
        {
          currentBreadcrumb: JSON.stringify(currentBreadcrumb),
        },
        '*',
      );
    };
    // iframe 节点挂在才可以获取到iframe节点,所以放入useEffect中
    if (iframeRef.current) {
      iframeRef.current.src = src;
      iframeRef.current.onload = handleMessage;
    }
    // 按钮权限和面包屑数据传递相关----------end
  }, [src, currentBreadcrumb]);
  return (
    <div className={styles.iframeContainer}>
      <iframe ref={iframeRef} className={styles.iframe} />
    </div>
  );
};
/** 子页面 */
const BasicLayout = ({ children }) => {
  const [currentBreadcrumb, setCurrentBreadcrumb] = useState([]);
  useEffect(() => {
    // 监听 message 事件回调
    const listener = (event) => {
      // 处理接收到的消息
      if (event?.data?.currentBreadcrumb) {
        setCurrentBreadcrumb(JSON.parse(event.data.currentBreadcrumb));
      }
    };

    // 监听 message 事件
    window.addEventListener('message', listener);
    return () => {
      // 移除监听[*打印发现,切换src时卸载事件不会走]
      window.removeEventListener('message', listener);
    };
  }, []);
  // 渲染面包屑
  const breadcrumbRender = () => {
    return (
      <Breadcrumb>
        {currentBreadcrumb.map((item) => (
          <Breadcrumb.Item key={item.key}>{item.label}</Breadcrumb.Item>
        ))}
      </Breadcrumb>
    );
  };
  return (
    <div className="wrapper">
      <div className="header">{breadcrumbRender()}</div>
    </div>
  );
};

【解决办法】在子页面加载完成时通知父页面(可以在useEffect中发送消息给父页面),父页面收到通知后给子页面传递数据。解决办法代码没有,需要自行实现

二、通过设置全局变量方式传递数据【仅适用于同源】

这里分两种情况:

  1. 变量放在子页面的全局,【问题】如果变量放在子页面的全局只有首次会有面包屑,切换菜单(即更改src)时面包屑消失,因为切换src,相当于加载了一个全新的页面,但是新页面会有自己的独立JavaScript上下文,因此原先iframe中的window对象及其上的变量会被销毁,新的页面会初始化一个新的window对象。虽然切换src时又给了window设置新值。但是可能在销毁之前。
  2. 变量放在父页面的全局,此时是可以传递到子页面中的,代码如下
/** 父页面 */
const IframePage = ({ src, currentBreadcrumb }) => {
  // iframe的ref
  const iframeRef = useRef();
  useEffect(() => {
    if (iframeRef.current) {
      iframeRef.current.src = src;
      window.currentBreadcrumb = currentBreadcrumb;
    }
    // 按钮权限和面包屑数据传递相关----------end
  }, [src, currentBreadcrumb]);
  return (
    <div className={styles.iframeContainer}>
      <iframe ref={iframeRef} className={styles.iframe} />
    </div>
  );
};

export default IframePage;
/** 子页面 */
const BasicLayout = ({ children }) => {
  // 获取并解析查询参数
  console.log('window', window.parent.currentBreadcrumb);
  // 渲染面包屑
  const breadcrumbRender = () => {
    return (
      <Breadcrumb>
        {(window.parent.currentBreadcrumb || []).map((item) => (
          <Breadcrumb.Item key={item.key}>{item.label}</Breadcrumb.Item>
        ))}
      </Breadcrumb>
    );
  };
  return (
    <div className="wrapper">
      <div className="header">{breadcrumbRender()}</div>
    </div>
  );
};

export default BasicLayout;

三、拼接到src中传递给子页面【适用于同源和非同源】

在设置src时将参数拼接到后面

/** 父页面 */
const IframePage = ({ src, currentBreadcrumb }) => {
  const iframeRef = useRef();
  useEffect(() => {
    if (!src) return;
    const currentUrl = new URL(src);
    if (iframeRef.current) {
      // 将面包屑数据发送给iframe页面
      currentUrl.searchParams.set('parentCurrentBreadcrumb', JSON.stringify(currentBreadcrumb));
      iframeRef.current.src = currentUrl;
    }
    // 按钮权限和面包屑数据传递相关----------end
  }, [currentBreadcrumb, src]);
  return (
    <div className={styles.iframeContainer}>
      <iframe ref={iframeRef} className={styles.iframe} />
    </div>
  );
};

export default IframePage;
/** 子页面 */
const BasicLayout = () => {
  // 获取并解析查询参数
  const urlParams = new URLSearchParams(window.location.search);
  // 面包屑数据
  const currentBreadcrumb = JSON.parse(urlParams.get('parentCurrentBreadcrumb')) || [];
  // 渲染面包屑
  const breadcrumbRender = () => {
    return (
      <Breadcrumb>
        {currentBreadcrumb.map((item) => (
          <Breadcrumb.Item key={item.key}>{item.label}</Breadcrumb.Item>
        ))}
      </Breadcrumb>
    );
  };
  return (
    <div className="wrapper">
      <div className="header">{breadcrumbRender()}</div>
    </div>
  );
};
export default BasicLayout;

iframe子页面向父页面传值

一、使用postMessage传递数据【适用于同源和非同源】

二、通过设置全局变量方式传递数据【仅适用于同源】