背景
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
需要手動重新包裝下,這樣才能像頁面一樣使用相關功能