<aside> 🔗 TOC

https://innei.in/posts/programming/why-i-prefer-imperative-modal

</aside>

什么是声明式 Modal

组件库中一般都会内置这类组件,最为参见的声明式 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 居然要写这么多代码,而且还是不可复用的,乱七八糟的状态。