背景
有个比较老的项目, 需要跨iframe
通讯. 主要原因是后端不想对所有接口开跨域, 而有部分通用信息需要保存在后端接口的统一域名下的localStorage
或cookie
中, 在无法去掉iframe
的情况下, 只能优化iframe
的调用过程了. 旧的基本都是直接使用document.createElement('iframe')
注入, 然后postMessage
返回结果, 对于ts
来说开发十分不友好, 基本全是any
类型
开发
page
这里实现接受iframe
传回的调用请求, 并将本地方法执行完成之后通过postMessage
发送回iframe
, 并提供一个调用iframe
里方法的接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| class Page { private init() { window.addEventListener('message', this.handleMessageEvent, false); } private async handleMessageEvent(event) { const handler = { callPageFn: async (data) => { const iframeId = data.iframeId; if (!iframeId) return; const iframe = this.iframeData[iframeId]; if (!iframe) return; if (data.fnId && localPageFunction.includes(data.fn) && iframe.$iframe && iframe.$iframe.contentWindow) { const result = await this.localFn[data.fn](data.params); iframe.$iframe.contentWindow.postMessage( JSON.stringify({ type: 'callPageFnCallback', fnId: data.fnId, result: result }), iframe.$iframe.getAttribute('src'), ); } }, }; try { const data: { type: typeof MessageType[number]; iframeId: string; [key: string]: any } = JSON.parse(event.data); handler[data.type](data); } catch (error) { return; } } private async transformCorsData<T = { [key: string]: string }>(fn: TransformCorsDataPageOptions['fn'], options: any = {}) { const $iframe = document.createElement('iframe'); const iframeId = this.generateIframeId(); $iframe.src = ``; $iframe.onload = () => { this.iframeData[iframeId].loaded = true; }; const $page = this.addIframe($iframe, '', 0, 0, false, iframeId, null, true); return await new Promise<T>((resolve) => { this.iframeData[iframeId] = { $dom: $page, type: 'transformCorsData', $iframe, options: { ...options, closeCb: (data: T) => resolve(data) }, loaded: false, }; }); } }
|
iframe
这里实现一次iframe
调用的接口, 调用完后去掉这个iframe
并将结果通过postMessage
传回页面, 同时可以通过Page
提供的callPageFn
调用页面提供的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class TransformCorsDataPage { options: TransformCorsDataPageOptions; private callPageFn: callPageFn; async init(options: TransformCorsDataPageOptions) { this.options = { ...options }; this.callPageFn = options.callPageFn; const result: { [key: string]: any } = await this[options.fn]?.(JSON.parse(decodeURIComponent(options.options))); postMessageToParent('close', this.options.referrer, result); } private async getIframeData(): Promise<IframeData> { return localStorage.getItem('iframeData'); } private async getPageData(): Promise<PageData> { const result = await this.callPageFn<PageData>({ method: 'get-page-data', data: {} }); return result; } }
const callPageFn: callPageFn = async <T>(fn, params) => { const currentFnId = (fnId++).toString(); const fnPromise = new Promise<T>((resolve) => { fnHandlerMap[currentFnId] = { resolve }; }); postMessageToParent('callPageFn', referrer, { fn, params, fnId: currentFnId }); return await fnPromise; };
export default new TransformCorsDataPage();
|
小结
这里只列出了核心的实现, 完成这部分通用逻辑后再想互相调用就可以使用比较简单的async/await
调用方法了, 省去了很多开发时间, 而且对ts
的支持比较好, 可以根据类型检测避免比较多的低级错误