<aside> 🔗 TOC
https://innei.in/posts/programming/why-i-prefer-imperative-modal
</aside>
组件库中一般都会内置这类组件,最为参见的声明式 Modal 定义。
例如 Antd 5 中的声明式 Modal 是这样定义的。
const App: React.FC = () => {
const [isModalOpen, setIsModalOpen] = useState(false)
const showModal = () => {
setIsModalOpen(true)
}
const handleOk = () => {
setIsModalOpen(false)
}
const handleCancel = () => {
setIsModalOpen(false)
}
return (
<>
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
<Modal
title="Basic Modal"
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</>
)
}
上面是一个受控的声明式 Modal 定义,写起来非常臃肿。你需要手动控制 Modal 的 Open 状态。并且你需要首先定义一个状态,然后在编写 UI,将状态和 UI 绑定。
这样的写法,我们需要在同一个组件定义一个状态,一个触发器(例如 Button)-> 控制状态 -> 流转到 Modal 显示。不仅写起来复杂,后期维护起来也很困难。
业务越积越多,后面你的页面上可能是这样的。
<>
<Button type="primary" onClick={showModal}>
Open Modal 1
</Button>
<Button type="primary" onClick={showModal}>
Open Modal 2
</Button>
{/* More buttons */}
<Modal
title="Basic Modal"
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
>
<p>Some contents...</p>
</Modal>
<Modal
title="Basic Modal 2"
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
>
<p>Some contents...</p>
</Modal>
<Modal
title="Basic Modal 3"
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
>
<p>Some contents...</p>
</Modal>
</>
一个组件中填满了无数个 Modal 和 Button。
这个时候你会想去抽离 Modal 到外部。像这样:
const App: React.FC = () => {
const [isModalOpen, setIsModalOpen] = useState(false)
const showModal = () => {
setIsModalOpen(true)
}
const handleOk = () => {
setIsModalOpen(false)
}
const handleCancel = () => {
setIsModalOpen(false)
}
return (
<>
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
<BaseModal1 {...{ isModalOpen, handleOk, handleCancel }} />
</>
)
}
const BaseModal1 = ({ isModalOpen, handleOk, handleCancel }) => {
return (
<Modal
title="Basic Modal"
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
>
<p>Some contents...</p>
</Modal>
)
}
然后你会发现控制 Modal 的状态还是在父组件顶层。导致父组件状态堆积越来越多。
const App: React.FC = () => {
const [isModalOpen, setIsModalOpen] = useState(false)
const [isModalOpen2, setIsModalOpen2] = useState(false)
const [isModalOpen3, setIsModalOpen3] = useState(false)
// ....
}
然后你思来想去,直接把 Modal 和 Button 抽离到一起。
const App: React.FC = () => {
return <BaseModal1 />
}
const BaseModal1 = () => {
const [isModalOpen, setIsModalOpen] = useState(false)
const showModal = () => {
setIsModalOpen(true)
}
const handleOk = () => {
setIsModalOpen(false)
}
const handleCancel = () => {
setIsModalOpen(false)
}
return (
<>
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
<Modal
title="Basic Modal"
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
>
<p>Some contents...</p>
</Modal>
</>
)
}
好了,这样 Button 和 Modal 直接耦合了,后续你想单独复用 Modal 几乎不可能了。
想来想去,再把 Modal 拆了。像这样:
const App: React.FC = () => {
return <BaseModal1WithButton />
}
const BaseModal1WithButton = () => {
const [isModalOpen, setIsModalOpen] = useState(false)
const showModal = () => {
setIsModalOpen(true)
}
const handleOk = () => {
setIsModalOpen(false)
}
const handleCancel = () => {
setIsModalOpen(false)
}
return (
<>
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
<BaseModal1 open={isModalOpen} onOk={handleOk} onCancel={handleCancel} />
</>
)
}
const BaseModal1 = ({ isModalOpen, handleOk, handleCancel }) => {
return (
<Modal
title="Basic Modal"
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
>
<p>Some contents...</p>
</Modal>
)
}
我去,为了解耦一个 Modal 居然要写这么多代码,而且还是不可复用的,乱七八糟的状态。