前端开发中,为了避免新开窗口,经常需要弹出模态框展示详情或者让用户操作。通常我们这样实现:
import Modal from './Modal'
class Demo extends React.Component {
constructor (props) {
super(props)
this.state = { showModal: false }
}
dispalyModal = () => this.setState({ showModal: true })
removeModal = () => this.setState({ showModal: false })
render () {
return (
<div>
<button onClick={ this.dispalyModal }>showModal</button>
{ this.state.showModal ? <Modal onClose={ this.removeModal }/> : null }
</div>
);
}
}
如果一个页面的模态框框较多,就会出现:
import Modal1 from './Modal1'
import Modal1 from './Modal2'
import Modal1 from './Modal3'
class Demo extends React.Component {
constructor (props) {
super(props)
this.state = {
showModal1: false,
showModal2: false,
showModal3: false,
}
}
dispalyModal1 = () => this.setState({ showModal1: true })
dispalyModal2 = () => this.setState({ showModal2: true })
dispalyModal3 = () => this.setState({ showModal3: true })
removeModal1 = () => this.setState({ showModal1: false })
removeModal2 = () => this.setState({ showModal2: false })
removeModal3 = () => this.setState({ showModal3: false })
render () {
const { showModal1, showModal2, showModal3 } = this.state;
return (
<div>
<button onClick={ this.dispalyModal1 }>showModal1</button>
<button onClick={ this.dispalyModal2 }>showModal2</button>
<button onClick={ this.dispalyModal3 }>showModal3</button>
{ showModal1 ? <Modal1 onClose={ this.removeModal1 }/> : null }
{ showModal2 ? <Modal2 onClose={ this.removeModal2 }/> : null }
{ showModal3 ? <Modal3 onClose={ this.removeModal3 }/> : null }
</div>
);
}
}
这样即使我们按功能拆分了组件,但组合时仍然显得十分复杂。典型的场景是文件列表,每个文件有重命名、复制、移动、删除、收藏等功能,且几乎每个功能都以模态框的形式呈现。
上面其实是“声明式”加载组件的,那么能不能用“命令式”,实现如下形式的调用呢?
import copyFile from './copyFile'
import moveFile from './moveFile'
import deleteFile from './deleteFile'
class Demo extends React.Component {
render () {
// files-i 是文件节点
const files = [file-0, file-1, file-2]
return (
<div>
<button onClick={ () => copyFile(files) }>copy</button>
<button onClick={ () => moveFile(files) }>move</button>
<button onClick={ () => deleteFile(files) }>delete</button>
</div>
);
}
}
为了实现上面的效果,我们需要一个能动态加载组件的函数。参考 ant-design 的 notification,经过探索,我实现了如下 mountAnywhere 函数:
import React from 'react'
import ReactDOM from 'react-dom'
function mountAnywhere(Comp, root) {
const div = document.createElement('div')
;(root || document.body).appendChild(div)
// 向组件注入 onClose 方法,以便组件能调用关闭
const Clone = React.cloneElement(Comp, {
onClose: () => {
ReactDOM.unmountComponentAtNode(div)
div.parentNode.removeChild(div)
}
});
ReactDOM.render(Clone, div)
}
export default mountAnywhere;
于是实现 copyFile 如下:
import mountAnywhere from './mountAnywhere'
class CopyModal extends React.Component {
onClose = () => {
// 这是 mountAnywhere 自动注入的方法,以便组件能触发关闭
this.props.onClose()
}
doCopy = () => {
const { files } = this.props;
// XXX: copy files...
}
render () {
const { files } = this.props;
return (
<div>
<button onClick={ this.onClose }>Cancel</button>
<button onClick={ this.doCopy }>OK</button>
</div>
)
}
}
function copyFile (files) {
mountAnywhere(<CopyModal files={ files }/>)
}
export default copyFile
这里主要用到了两个 ReactDOM API,ReactDOM.render 和 ReactDOM.unmountComponentAtNode,由此实现了在 React 中命令式载入组件。