思路如下:
使用window.top添加元素到顶层页面,
通过Portal将子组件渲染到该元素,
在iframe内部可以写弹窗内的布局,
在window.top添加样式和方法,
在找到对应的元素设置监听onclick/oninput方法,
然后就可以操作mobx数据,
使顶级页面和iframe进行交互。
相关代码如下:
- iframeUtil.js
/**
* 在文档 iframeDocument 中添加样式
* @param {*} iframeDocument
* @param {*} cssText
*/
const addCSS = (iframeDocument, cssText) => {
var style = iframeDocument.createElement('style'), //创建一个style元素
head = iframeDocument.head || iframeDocument.getElementsByTagName('head')[0]; //获取head元素
style.type = 'text/css'; //这里必须显示设置style元素的type属性为text/css,否则在ie中不起作用
if (style.styleSheet) {
//IE
var func = function() {
try {
//防止IE中stylesheet数量超过限制而发生错误
style.styleSheet.cssText = cssText;
} catch (e) {}
};
//如果当前styleSheet还不能用,则放到异步中则行
if (style.styleSheet.disabled) {
setTimeout(func, 10);
} else {
func();
}
} else {
//w3c
//w3c浏览器中只要创建文本节点插入到style元素中就行了
var textNode = iframeDocument.createTextNode(cssText);
style.appendChild(textNode);
}
head.appendChild(style); //把创建的style元素插入到head中
};
const addTopModal = () => {
const topModal = document.createElement('div');
topModal.setAttribute('id', 'topModal');
topModal.setAttribute(
'style',
'position:absolute;z-index:999;width:100%;height:100%;background:rgba(0,0,0,0.5);overflow:hidden;top: 0;bottom: 0;left: 0;right: 0;'
);
window.top.document.body.appendChild(topModal);
};
/**
* 添加全局的 topModal
* 如果已存在,那么不做处理,
* 如果不存在,那么添加。
* @returns
*/
const addTopModalAndCheckExist = () => {
let topModal = window.top.document.getElementById('topModal');
if (!topModal) {
addTopModal();
topModal = window.top.document.getElementById('topModal');
}
window.top.topModal = topModal;
return topModal;
};
/**
* 移除全局的 topModal
* @returns
*/
const removeTopModal = () => {
if (window.top.topModal) {
if (window.top.topModal.parentElement) {
window.top.topModal.parentElement.removeChild(window.top.topModal);
}
window.top.topModal = null;
}
};
/**
* 初始化全局的 topModal
* IframeServicesCatalog 相关
* 每一个弹窗要维护自己的 init 方法
* 内嵌iframe只需要提供关闭弹窗功能
* 其他的内容都是可以直接展示出来的
*
* @returns
*/
const initTopModalIframeServicesCatalog = () => {
const eleClose = window.top.document.getElementById('iframeServicesCatalog-close');
eleClose.onclick = window.top.customTopModal.onClickClose;
};
/**
* 初始化全局的 topModal
* TopModalForm 相关
* 每一个弹窗要维护自己的 init 方法
* 在顶级页面弹窗展示form表单,
* 并设置相关监听,
* 同步数据到mobx,
* TopModalForm 示例添加;
*
*
* @returns
*/
const initTopModalForm = () => {
const eleClose = window.top.document.getElementById('TopModal-close');
eleClose.onclick = window.top.customTopModal.onClickClose;
const btnUpdateMobxData = window.top.document.getElementById('customTopModal.btnUpdateMobxData');
btnUpdateMobxData.onclick = window.top.customTopModal.onClickUpdateMobxData;
const inputUpdateMobxData = window.top.document.getElementById('customTopModal.inputUpdateMobxData');
// inputUpdateMobxData.onchange = window.top.customTopModal.onChangeInput; // 这个无效 input监听需要使用oninput
inputUpdateMobxData.oninput = window.top.customTopModal.onChangeInput;
};
const customIframe = { addCSS };
const customTopModal = {
initTopModalIframeServicesCatalog,
initTopModalForm,
addTopModalAndCheckExist,
removeTopModal
};
//全局调用 iframe 相关方法
window.top.customIframe = customIframe;
//全局调用 topModal 相关方法
window.top.customTopModal = customTopModal;
export { customIframe, customTopModal };
- Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。
components/topModal.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { customTopModal } from '@/utils/iframeUtil';
class TopModal extends React.Component {
constructor(props) {
super(props);
customTopModal.addTopModalAndCheckExist();
this.el = document.createElement('div');
}
componentDidMount() {
// 在 Modal 的所有子元素被挂载后,
// 这个 portal 元素会被嵌入到 DOM 树中,
// 这意味着子元素将被挂载到一个分离的 DOM 节点中。
// 如果要求子组件在挂载时可以立刻接入 DOM 树,
// 例如衡量一个 DOM 节点,
// 或者在后代节点中使用 ‘autoFocus’,
// 则需添加 state 到 Modal 中,
// 仅当 Modal 被插入 DOM 树中才能渲染子元素。
window.top.topModal.appendChild(this.el);
}
componentWillUnmount() {
customTopModal.removeTopModal();
}
render() {
return ReactDOM.createPortal(this.props.children, this.el);
}
}
export default TopModal;
- stores/TopModalForm.js mobx 数据管理
import { observable, action, runInAction } from 'mobx';
// import { querySrvcatsWithoutChild } from '@/services/api';
export default class TopModalForm {
@observable
data = [];
@observable
inputValue = '';
@action.bound
getData = async () => {
// const res = await querySrvcatsWithoutChild();
const menuNames = ['a', 'b', 'c', 'd'];
runInAction(() => {
this.data = menuNames;
});
};
@action.bound
onChangeInput = async value => {
this.inputValue = value;
};
@action.bound
onClickUpdateMobxData = async () => {
this.inputValue = `value${this.inputValue}`;
};
}
- routes/TopModalForm/customStyle.js 要添加到window.top 的样式
const borderStyle = '1px solid #e6eaef';
let topModal = `
#TopModalForm {
margin: 0 auto;
}
.TopModalForm-wrap {
width: 70%;
margin: 80px auto;
height: 640px;
background: #12335B;
}
.TopModalForm-header {
border-bottom: ${borderStyle};
display: flex;
justify-content: space-between;
align-items: center;
font-size: 20px;
padding: 10px;
}
`;
// let isPreviewPage = ".isPreviewPage {background: red;}"
let styleText = topModal;
// +isPreviewPage
export default styleText;
- routes/TopModalForm/TopModalForm.js
import React, { Component } from 'react';
import { observer, inject } from 'mobx-react';
import { Icon, Input, Button } from '@xxx/components';
import TopModal from '@/components/topModal';
import { withRouter } from 'react-router';
import gd_icon from '../../../public/static/images/gd_icon.png';
import { customTopModal } from '@/utils/iframeUtil';
@withRouter
@inject('topModalFormStore')
@observer
class TopModalForm extends Component {
constructor(props) {
super(props);
// todo 需要在构造器里面初始化相关方法才能在当前页面正常使用
window.top.customTopModal.onChangeInput = this.onChangeInput;
window.top.customTopModal.onClickUpdateMobxData = this.onClickUpdateMobxData;
this.state = {};
}
onClickUpdateMobxData = e => {
console.log('onClickUpdateMobxData.e', e);
const { inputValue } = this.props.topModalFormStore;
console.log('onClickUpdateMobxData.inputValue', inputValue);
const { onClickUpdateMobxData } = this.props.topModalFormStore;
onClickUpdateMobxData();
};
onChangeInput = e => {
console.log('onChangeInput.e', e);
const value = e.target.value;
const { onChangeInput } = this.props.topModalFormStore;
onChangeInput(value);
const { inputValue } = this.props.topModalFormStore;
console.log('onChangeInput.inputValue', inputValue);
};
initData = async () => {
customTopModal.initTopModalForm();
};
componentDidMount() {
this.initData();
}
componentWillUnmount() {}
render() {
const { title, data: dataProps } = this.props;
const { data, inputValue } = this.props.topModalFormStore;
return (
<div className="">
<TopModal>
<div className="TopModalForm-wrap">
<div className="TopModalForm-header">
<span>{title}</span>
<Icon type="close" id="TopModal-close" onClick={window.top.customTopModal.onClickClose} />
</div>
<div className="TopModalForm-main">
<div>
本地图片使用示例:
<img src={gd_icon} className="" alt="暂无图片"></img>
</div>
<div>
props数据使用示例:
{dataProps.map((item, i) => {
return item.name;
})}
</div>
<div>
mobx数据使用示例:
{data.map((item, i) => {
return item;
})}
</div>
<div>mobx数据使用示例: 输入框内容为{inputValue}</div>
<Input
value={inputValue}
id="customTopModal.inputUpdateMobxData"
// onChange={window.top.customTopModal.onChangeInput} // 这样写是无效的 需要通过id获取元素并设置监听
/>
<Button
id="customTopModal.btnUpdateMobxData"
// onClick={window.top.customTopModal.onClickUpdateMobxData} // 这样写是无效的 需要通过id获取元素并设置监听
>
更新mobx数据
</Button>
</div>
</div>
</TopModal>
</div>
);
}
}
export default TopModalForm;
- routes/TopModalForm/TopModalFormEntry.js
import React, { Component } from 'react';
import { observer, inject } from 'mobx-react';
import { withRouter } from 'react-router';
import styleText from './customStyle';
import TopModalForm from './TopModalForm';
import { customIframe, customTopModal } from '@/utils/iframeUtil';
@withRouter
@inject('topModalFormStore')
@observer
class TopModalFormEntry extends Component {
constructor(props) {
super(props);
this.state = {
visibleTopModal: false,
title: 'title'
};
}
initData = async () => {
this.props.topModalFormStore.getData();
customIframe.addCSS(window.top.document, styleText);
window.top.customTopModal.onClickClose = this.onClickClose;
};
componentDidMount() {
this.initData();
}
componentWillUnmount() {}
onClickClose = () => {
console.log('onClick 关闭');
this.setState({
visibleTopModal: false
});
};
showTopModal = () => {
console.log('onClick 展示');
this.setState({
visibleTopModal: true
});
};
render() {
const { title, visibleTopModal } = this.state;
const { data } = this.props.topModalFormStore;
return (
<div className="TopModalFormEntry">
<div
onClick={() => {
this.showTopModal();
}}
>
子级页面 TopModalForm
{data.map((item, i) => {
return item;
})}
</div>
{visibleTopModal && <TopModalForm title={title} data={data} />}
</div>
);
}
}
export default TopModalFormEntry;
参考链接:
- Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。
https://react.docschina.org/docs/portals.html
网友评论