背景
web 端日常开发经常会遇到各种弹层需求,弹层内容千奇百怪。根据我们日常的组件化开发经验,我们会把弹层分为 2 个组件
- 弹层组件:包含
蒙版
,展示隐藏
逻辑 - 内容组件:本次需求实际组件
使用
import CitySelect from './components/CitySelect';
import modal from '@/components/modal';
...
openCitySelect() {
modal.popup({
content: CitySelect,
props: {
data,
onSave: () => {...}
}
});
}
...
说明
这种用法的好处是显而易见的
-
调用方
与CitySelect
都不需要维护一个visible
变量 -
调用方
与CitySelect
模块分离更加清晰,调用方
只需要触发一次openCitySelect
,之后就和弹层再无关系,CitySelect
只关心自身逻辑不需要关心弹层的状态。
下面来看此 api 在 vue
和react
上面的实现
Vue 实现
vue 下面实现此 api
的方法较多,可以使用 dom 操作
, 动态组件
, render 函数
来实现。我们以 动态组件
为例:
<template>
<div class="container" v-show="visible">
<div class="mask"></div>
<component :is="currentView" v-if="currentView" ref="comp" :v-bind="propsData"></component>
</div>
</template>
<script>
const Popup = {
props: {title: { type: String, default: '提示'},
propsData: {type: Object},
currentView: {type: Object},
},
data () {
return {visible: false}
},
mounted () {
// 创建后直接打开
this.$nextTick(this.open)
},
methods: {open () {this.visible = true;},
close () {
this.visible = false;
// 移除元素
if (this.$el.parentNode) {this.$el.parentNode.removeChild(this.$el)
}
}
}
}
export default {popup({ content, title, props}) {let Comp = Vue.extend(Popup);
let instance = new Comp({
propsData: {
propsData: props,
currentView: content,
title
}
}).$mount();
document.appendChild(instance.$el);
}
};
</script>
React 实现
React 实现需要配合 react-dom
,rn 的话可以再 app 入口加一个常驻弹窗容器来实现,原理大致相同。
这里还是以 web 端为例,代码直接从 ant
抄过来的。
// 弹窗组件
class Popup extends Component {componentDidMount() { }
render() {
const {
close,
props = {},
visible = false,
} = this.props;
if (!visible) {return null;}
const DlgContent = content;
return (
<Router>
<Provider {...store}>
<div className={styles.container}>
<div className="mask"></div>
<DlgContent close={close} {...props} />
</div>
</Provider>
</Router>
)
}
}
// 方法
const popup = function(config) {const div = document.createElement('div');
document.body.appendChild(div);
let currentConfig = {...config, visible: true, close};
function close(...args) {
currentConfig = {
...currentConfig,
visible: false
};
render(currentConfig);
setTimeout(destroy, 100);
}
function destroy() {const unmountResult = ReactDOM.unmountComponentAtNode(div);
if (unmountResult && div.parentNode) {div.parentNode.removeChild(div);
}
}
function render(config) {ReactDOM.render(<Popup {...config} />, div);
}
render(currentConfig);
return {destroy: close};
};
const modal = {popup} ;
export default modal;
注意
由于弹窗是我们手动 new
出来的,并没有包含在入口的 jsx 引用中,所以类似 router
,store
,locale
这些 Provider
需要手动重新包装下,这样才能像页面一样使用相关功能