1、Postmate 介绍
是一款基于 postMessage 来处理父子页面通信的库,轻量且好用。一个强大的、简单的、基于 promise 的 postMessage iFrame 通信库。
postmate 官方地址
https://github.com/dollarshaveclub/postmate
2、Postmate 特性
基于 promise 的 API,用于优雅和简单的通信。
安全的双向父 <-> 子握手,并带有消息验证。
子代暴露一个可检索的模型对象,父代可以访问。
子代发出事件,父代可以监听。
父代可以调用子代的函数。
零依赖性。如果需要的话,可以为 Promise API 提供自己的 polyfill 或抽象。
轻量级,大小约为 1.6kb(缩小和压缩后)。
3、Postmate 安装
//yarn
$ yarn add postmate
//npm
$ npm i postmate --save
4、Postmate 使用
这里提供一个 demo 内含 React 和 Vue 两个项目,其中 React 项目为父项目,Vue 项目为子项目,启动后,父项目会通过 iframe 方式自动加载子项目。
#父项目react
cd react
yarn
yarn start
#子项目vue-demo
cd vue-demo
yarn
yarn serve
-
3、项目运行时截图和视频
截图一:
截图二:
完整流程如下:
-
4、重要代码详解,父项目 react
import { useEffect, useRef, useState } from "react";
import yayJpg from "../assets/yay.jpg";
import PostMate from "postmate";
import type { ParentAPI, PostmateOptions } from "postmate";
import "./index.less";
function HomePage() {
const childRef = useRef<any>(null);
const [loading, setLoading] = useState(false);
// 初始化
const init = (params: PostmateOptions) => {
console.log("++++++++++parent++++++++: 初始化parent页面postmate实例");
const handshake = new PostMate({ ...params });
// 回调
handshake
.then((child) => {
console.log(
"++++++++++parent++++++++: 接收到child页面握手成功后,回传消息"
);
// 存储child 到 childRef中
childRef.current = child;
child.on("some-event", (data) => {
console.log(data);
initChildParams();
});
// 更新UI
updateStyle(child);
})
.catch((error) => {
console.log(error);
});
};
useEffect(() => {
init({
container: document.getElementById("iframe-container"),
url: "http://localhost:8080/",
name: "my-iframe-name",
classListArray: ["my-class"],
});
return () => {
// 销毁iframe
childRef.current.destroy();
};
}, []);
// 更新UI
const updateStyle = (child: ParentAPI) => {
// 采用轮训方式获取child页面高度
setInterval(() => {
child.get("height").then((height) => {
child.frame.style.height = `${height + 80}px`;
});
}, 100);
};
// 设置child页面参数
const initChildParams = () => {
console.log(
"++++++++++parent++++++++: 调用child页面中defaultValues方法,设置表单默认参数"
);
childRef.current.call(
"defaultValues",
JSON.stringify({
userErp: "yangyujiao5", //当前用户的erp,表单需要
bamboolUrl: "bamboolUrl", //测试包链接
sdkName: "sdkName", //SDK名称
sdkVersion: "sdkVersion", //SDK版本
})
);
};
// 触发child页面的提交函数
const onChildSubmitChange = (params: Record<string, any>) => {
return new Promise<any>((resolve, reject) => {
console.log(
"++++++++++parent++++++++: 调用child页面中onSubmit方法,触发child表单提交"
);
// 触发child页面的onChildSubmit方法
childRef.current.call("onSubmit", { ...params });
// 在parent页面注册事件,用来获取child页面submit-callback方法
childRef.current.on("submit-callback", (data: any) => {
console.log(
"++++++++++parent++++++++: 接收到child页面表单提交成功后通知"
);
if (data?.code === 200) {
resolve(data);
} else {
reject();
}
});
});
};
// parent提交函数
const onSubmit = async () => {
console.log("++++++++++parent++++++++: 触发parent页面表单提交方法");
try {
setLoading(true);
const { code, sdkID } = await onChildSubmitChange({ id: 21 });
console.log(code, sdkID);
// return {code, sdkID}
// child表单提交成工后,触发parent页面提交
if (code !== 200) return;
console.log(
"++++++++++parent++++++++: parent页面表单提交成功后,调用child页面onFinishCallback方法"
);
//parent页面提交任务结束后,通知child页面
childRef.current.call("onFinishCallback", { parent_id: 2 });
// 在parent页面注册finish-callback,等到该方法执行完成后,则完成一次iframe交互
childRef.current.on("finish-callback", (data: any) => {
console.log(
"++++++++++parent++++++++: 接收到child页面完成onFinishCallback通知",
data
);
if (data?.code === 200) {
setLoading(false);
}
});
} catch (error) {
console.log(error);
}
};
return (
<div>
<h2>Yay! Welcome to umi!</h2>
<p>
<img src={yayJpg} width="388" />
</p>
<button onClick={onSubmit}>{loading ? "正在提交..." : "点击提交"}</button>
<div id="iframe-container" style={{ overflow: "hidden" }}></div>
</div>
);
}
export default HomePage;
子项目 vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Welcome to Your Vue.js App" />
来自parent的信息:{{ JSON.stringify(formValues) }}
</div>
</template>
<script>
import Postmate from "postmate";
import HelloWorld from "./components/HelloWorld.vue";
const ajax = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
code: 200, //结果code,200为成功
sdkID: "SDKID", //SDKID
error: "1000", //错误信息
});
}, 2000);
});
};
export default {
name: "App",
data() {
return {
parent: null,
formValues: {},
};
},
components: {
HelloWorld,
},
mounted() {
this.init();
},
methods: {
// 初始化
init() {
console.log("---------child------: 初始化postmate配置");
let _this = this;
//options: 从父页面传入的参数信息
//function:提供给父页面的方法
const handshake = new Postmate.Model({
// 返回当前页面高度
height: () => document.height || document.body.offsetHeight,
// 提供给parent的设置表单默认数据方法
defaultValues: (values) => {
_this.onDefaultValues(JSON.parse(values));
},
// 提供给parent的表单提交方法
onSubmit: (values) => {
_this.onSubmit(values);
},
// 提供给parent的表单完成回调方法
onFinishCallback: (params) => {
_this.onFinishCallback(params);
},
});
// 握手成功后回调
handshake.then((child) => {
console.log("---------child------: 与parent页面握手成功", child);
child.emit("some-event", "child接收到信息了");
_this.parent = child;
});
},
// 设置表单字段值
onDefaultValues(params) {
console.log("---------child------: 设置child页面表单默认值", params);
this.formValues = params;
},
// 提交表单函数
async onSubmit(params) {
let _this = this.parent;
console.log("---------child------: 触发child页面表单提交", params);
const oldValues = JSON.parse(JSON.stringify(this.formValues));
console.log({ ...params, ...oldValues });
// 模拟执行异步任务,提交表单
const { code } = await ajax();
if (code === 200) {
console.log(
"---------child------: child页面表单提交成功,调用parent页面submit-callback方法,并回传参数"
);
_this.emit("submit-callback", {
code: 200, //结果code,200为成功
sdkID: "SDKID", //SDKID
error: "1000", //错误信息
});
}
},
// parent页面表单提交完成后,回调函数
onFinishCallback(params) {
let _this = this.parent;
console.log("---------child------: parent页面表单提交完成后,触发child回调函数",params);
setTimeout(() => {
console.log("---------child------: child页面完成并触发parent的finish-callback方法");
_this.emit("finish-callback", {
code: 200, //结果code,200为成功
sdkID: "SDKID", //SDKID
error: "1000", //错误信息
});
}, 2000);
},
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
</style>
如果涉及到内嵌多个 iframe 的情况下,可参考以下 postmates-js 实现
https://gitee.com/videring/postmates-js