以前的需求有一些彈窗,彈窗內容比較複雜,在 React 項目中怎麼方便高效地實現一個業務彈窗組件呢?經歷了一段時間的演變,終於找到了比較方便的方法……bash
下面以簡單的設置文案的彈窗爲例列舉一下彈窗的實現。antd
使用的基礎彈窗組件是 antd modal。app
比較常見的寫法就是經過標籤實現,也是我最開始經常使用的,但總以爲不爽。ui
🎉 完整 demothis
彈窗組件的實現:spa
class DialogCustom extends React.Component {
constructor(props) {
super(props);
this.state = { text: '' };
}
handleOk = () => {
this.props.onOk(this.state.text);
this.props.onClose();
}
onChange = (e) => {
this.setState({ text: e.target.value });
}
render() {
const { visible, onClose } = this.props;
return (
<Modal title="設置文案" visible={visible} onOk={this.handleOk} onCancel={onClose}>
<Input value={this.state.text} onChange={this.onChange} />
</Modal>
);
}
}
複製代碼
使用組件:code
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: false, // 要增長一個 visible 很麻煩有木有
text: ''
};
}
handleOk = (v) => {
this.setState({ text: v });
}
showDialog = () => {
this.setState({ visible: true });
}
handleClose = () => {
this.setState({ visible: false });
}
render() { // 只能經過標籤調用
return (<div>
<Button onClick={this.showDialog}>設置文案</Button>
<DialogCustom visible={this.state.visible} onOk={this.handleOk} onClose={this.handleClose} />
<div>{this.state.text}</div>
</div>);
}
}
複製代碼
本方法的麻煩之處在於:component
實現一個彈窗組件時每次都要引入 Modal 組件;cdn
使用組件時,只能用標籤的方式,且須要有一個 visible state 控制彈窗狀態;blog
這樣寫一個其實也沒什麼,多了之後就煩了……
針對上面使用組件的麻煩,但願使用組件時能夠經過 xxx.show()
的方式快捷調用,不須要標籤,不須要經過 visible state 控制。
解決:給組件加上 show 方法。跟上一方法的差異就是增長了一個 show 方法,另外 <Modal />
的 visible
屬性直接設爲 true
便可
🎉 完整 demo
彈窗組件的實現:
class DialogCustom extends React.Component {
static show = params => {
let container = document.createElement("div");
document.body.appendChild(container);
function closeHandle() {
ReactDOM.unmountComponentAtNode(container);
document.body.removeChild(container);
container = null;
}
ReactDOM.render(<DialogCustom {...params} onClose={closeHandle} />, container);
};
...
render() {
return (<Modal ... visible={true}>...</Modal>);
}
}
複製代碼
使用組件:
DialogCustom.show({ onOk: this.handleOk }); // 調用 show 想彈就彈,彈得響亮;直接可關閉,不用再增長 visible state 去控制
複製代碼
本方法的麻煩之處在於:
這樣寫幾個,多了之後仍是煩了……
針對 「實現一個彈窗組件時每次都要引入 Modal,都要重寫一次 show 方法」 的問題,能夠用高階組件解決
🎉 完整 demo
高階組件:
const withDialog = WrappedComponent => {
function EnhancedComponent(props) {
const { title, onClose, ...others } = props;
return (<Modal visible={true} title={title || WrappedComponent.title} footer={<div />}>
<WrappedComponent {...others} onClose={onClose} />
</Modal>);
}
EnhancedComponent.show = params => {
let container = document.createElement("div");
document.body.appendChild(container);
function closeHandle() {
ReactDOM.unmountComponentAtNode(container);
document.body.removeChild(container);
container = null;
}
ReactDOM.render(<EnhancedComponent {...params} onClose={closeHandle} />, container);
};
return EnhancedComponent;
};
複製代碼
設置文案的組件幾乎只要處理自己的邏輯,跟彈窗相關的有兩個點:
若是不須要彈窗了,那麼不使用高階組件,直接使用 SetText 便可(此處應該要再處理下 footer)。
業務組件:
@withDialog // 使用高階組件,很關鍵
class SetText extends React.Component { // 只要處理自己的邏輯,幾乎不用在乎彈窗
static title = "設置文案";
static defaultProps = {
onClose: () => {}
};
constructor(props) {
super(props);
this.state = { text: "" };
}
onChange = e => {
this.setState({ text: e.target.value });
};
handleOk = () => {
this.props.onOk(this.state.text);
this.props.onClose();
};
render() {
return (<div>
<Input value={this.state.text} onChange={this.onChange} />
<div>
<Button onClick={this.handleOk}>肯定</Button>
<Button onClick={this.props.onClose}>取消</Button>
</div>
</div>);
}
}
複製代碼
使用組件:
SetText.show({ onOk: this.handleOk });
複製代碼
本方法的問題在於:
經過這種方式,幾乎不用侵入你的組件,你的組件就能夠披上一個彈窗。
注:文本案例基於 antd modal 實現,但不限於 antd modal,你能夠本身封裝一個基礎彈窗就能夠實現彈彈彈。