Javascript 如何在 Redux 中显示执行异步操作的模式对话框?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35623656/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 18:01:55  来源:igfitidea点击:

How can I display a modal dialog in Redux that performs asynchronous actions?

javascriptmodal-dialogreduxreact-redux

提问by carlesba

I'm building an app that needs to show a confirm dialog in some situations.

我正在构建一个在某些情况下需要显示确认对话框的应用程序。

Let's say I want to remove something, then I'll dispatch an action like deleteSomething(id)so some reducer will catch that event and will fill the dialog reducer in order to show it.

假设我想删除一些东西,然后我将发送一个动作,deleteSomething(id)这样一些减速器将捕获该事件并填充对话框减速器以显示它。

My doubt comes when this dialog submits.

当这个对话框提交时,我的怀疑就来了。

  • How can this component dispatch the proper action according to the first action dispatched?
  • Should the action creator handle this logic?
  • Can we add actions inside the reducer?
  • 这个组件如何根据调度的第一个动作调度正确的动作?
  • 动作创建者应该处理这个逻辑吗?
  • 我们可以在减速器中添加动作吗?

edit:

编辑:

to make it clearer:

更清楚地说明:

deleteThingA(id) => show dialog with Questions => deleteThingARemotely(id)

createThingB(id) => Show dialog with Questions => createThingBRemotely(id)

So I'm trying to reuse the dialog component. Showing/hiding the dialog it's not the problem as this can be easily done in the reducer. What I'm trying to specify is how to dispatch the action from the right side according to the action that starts the flow in the left side.

所以我试图重用对话框组件。显示/隐藏对话框不是问题,因为这可以在减速器中轻松完成。我试图指定的是如何根据在左侧启动流程的操作从右侧分派操作。

回答by Dan Abramov

The approach I suggest is a bit verbose but I found it to scale pretty well into complex apps. When you want to show a modal, fire an action describing whichmodal you'd like to see:

我建议的方法有点冗长,但我发现它可以很好地扩展到复杂的应用程序中。当你想显示一个模态时,触发一个描述你想看到哪个模态的动作:

Dispatching an Action to Show the Modal

调度一个动作来显示模态

this.props.dispatch({
  type: 'SHOW_MODAL',
  modalType: 'DELETE_POST',
  modalProps: {
    postId: 42
  }
})

(Strings can be constants of course; I'm using inline strings for simplicity.)

(当然,字符串可以是常量;为了简单起见,我使用内联字符串。)

Writing a Reducer to Manage Modal State

编写一个 Reducer 来管理模态状态

Then make sure you have a reducer that just accepts these values:

然后确保你有一个只接受这些值的减速器:

const initialState = {
  modalType: null,
  modalProps: {}
}

function modal(state = initialState, action) {
  switch (action.type) {
    case 'SHOW_MODAL':
      return {
        modalType: action.modalType,
        modalProps: action.modalProps
      }
    case 'HIDE_MODAL':
      return initialState
    default:
      return state
  }
}

/* .... */

const rootReducer = combineReducers({
  modal,
  /* other reducers */
})

Great! Now, when you dispatch an action, state.modalwill update to include the information about the currently visible modal window.

伟大的!现在,当您调度一个动作时,state.modal将更新以包含有关当前可见模态窗口的信息。

Writing the Root Modal Component

编写根模态组件

At the root of your component hierarchy, add a <ModalRoot>component that is connected to the Redux store. It will listen to state.modaland display an appropriate modal component, forwarding the props from the state.modal.modalProps.

在组件层次结构的根部,添加一个<ModalRoot>连接到 Redux 存储的组件。它将侦听state.modal并显示适当的模态组件,从state.modal.modalProps.

// These are regular React components we will write soon
import DeletePostModal from './DeletePostModal'
import ConfirmLogoutModal from './ConfirmLogoutModal'

const MODAL_COMPONENTS = {
  'DELETE_POST': DeletePostModal,
  'CONFIRM_LOGOUT': ConfirmLogoutModal,
  /* other modals */
}

const ModalRoot = ({ modalType, modalProps }) => {
  if (!modalType) {
    return <span /> // after React v15 you can return null here
  }

  const SpecificModal = MODAL_COMPONENTS[modalType]
  return <SpecificModal {...modalProps} />
}

export default connect(
  state => state.modal
)(ModalRoot)

What have we done here? ModalRootreads the current modalTypeand modalPropsfrom state.modalto which it is connected, and renders a corresponding component such as DeletePostModalor ConfirmLogoutModal. Every modal is a component!

我们在这里做了什么?ModalRoot读取它所连接的电流modalTypemodalProps来源state.modal,并呈现相应的组件,例如DeletePostModalConfirmLogoutModal。每个模态都是一个组件!

Writing Specific Modal Components

编写特定的模态组件

There are no general rules here. They are just React components that can dispatch actions, read something from the store state, and just happen to be modals.

这里没有一般规则。它们只是 React 组件,可以分派动作,从 store 状态中读取一些东西,并且恰好是 modals

For example, DeletePostModalmight look like:

例如,DeletePostModal可能看起来像:

import { deletePost, hideModal } from '../actions'

const DeletePostModal = ({ post, dispatch }) => (
  <div>
    <p>Delete post {post.name}?</p>
    <button onClick={() => {
      dispatch(deletePost(post.id)).then(() => {
        dispatch(hideModal())
      })
    }}>
      Yes
    </button>
    <button onClick={() => dispatch(hideModal())}>
      Nope
    </button>
  </div>
)

export default connect(
  (state, ownProps) => ({
    post: state.postsById[ownProps.postId]
  })
)(DeletePostModal)

The DeletePostModalis connected to the store so it can display the post title and works like any connected component: it can dispatch actions, including hideModalwhen it is necessary to hide itself.

DeletePostModal连接到存储,以便它可以显示文章标题和作品就像任何连接的组件:可以调度的行动,包括hideModal在必要时可以隐藏自身。

Extracting a Presentational Component

提取展示组件

It would be awkward to copy-paste the same layout logic for every “specific” modal. But you have components, right? So you can extract a presentational<Modal>component that doesn't know what particular modals do, but handles how they look.

为每个“特定”模态复制粘贴相同的布局逻辑会很尴尬。但是你有组件,对吧?因此,您可以提取一个表示<Modal>组件,该组件不知道特定模态的作用,但处理它们的外观。

Then, specific modals such as DeletePostModalcan use it for rendering:

然后,特定的模态,例如DeletePostModal可以使用它进行渲染:

import { deletePost, hideModal } from '../actions'
import Modal from './Modal'

const DeletePostModal = ({ post, dispatch }) => (
  <Modal
    dangerText={`Delete post ${post.name}?`}
    onDangerClick={() =>
      dispatch(deletePost(post.id)).then(() => {
        dispatch(hideModal())
      })
    })
  />
)

export default connect(
  (state, ownProps) => ({
    post: state.postsById[ownProps.postId]
  })
)(DeletePostModal)

It is up to you to come up with a set of props that <Modal>can accept in your application but I would imagine that you might have several kinds of modals (e.g. info modal, confirmation modal, etc), and several styles for them.

您可以想出一组<Modal>可以在您的应用程序中接受的道具,但我想您可能有多种模态(例如信息模态、确认模态等),以及它们的多种样式。

Accessibility and Hiding on Click Outside or Escape Key

可访问性和隐藏点击外部或退出键

The last important part about modals is that generally we want to hide them when the user clicks outside or presses Escape.

关于模态的最后一个重要部分是,通常我们希望在用户单击外部或按下 Escape 时隐藏它们。

Instead of giving you advice on implementing this, I suggest that you just don't implement it yourself. It is hard to get right considering accessibility.

我建议您不要自己实施,而不是就实施此问题提供建议。考虑到可访问性,很难做到正确。

Instead, I would suggest you to use an accessibleoff-the-shelf modal component such as react-modal. It is completely customizable, you can put anything you want inside of it, but it handles accessibility correctly so that blind people can still use your modal.

相反,我建议您使用可访问的现成模态组件,例如react-modal. 它是完全可定制的,你可以在里面放任何你想要的东西,但它正确地处理了可访问性,这样盲人仍然可以使用你的模态。

You can even wrap react-modalin your own <Modal>that accepts props specific to your applications and generates child buttons or other content. It's all just components!

您甚至react-modal可以自己包装<Modal>接受特定于您的应用程序的道具并生成子按钮或其他内容。一切都只是组件!

Other Approaches

其他方法

There is more than one way to do it.

有不止一种方法可以做到这一点。

Some people don't like the verbosity of this approach and prefer to have a <Modal>component that they can render right inside their componentswith a technique called “portals”. Portals let you render a component inside yours while actuallyit will render at a predetermined place in the DOM, which is very convenient for modals.

有些人不喜欢这种方法的冗长,而更喜欢有一个<Modal>组件,他们可以使用称为“门户”的技术直接在其组件内部呈现。Portals 允许你在你的内部渲染一个组件,而实际上它会在 DOM 中的预定位置渲染,这对于模态来说非常方便。

In fact react-modalI linked to earlier already does that internally so technically you don't even need to render it from the top. I still find it nice to decouple the modal I want to show from the component showing it, but you can also use react-modaldirectly from your components, and skip most of what I wrote above.

事实上,react-modal我之前链接到的已经在内部这样做了,所以从技术上讲,你甚至不需要从顶部渲染它。我仍然觉得将我想要显示的模态与显示它的组件分离是很好的,但是您也可以react-modal直接从组件中使用,并跳过我上面写的大部分内容。

I encourage you to consider both approaches, experiment with them, and pick what you find works best for your app and for your team.

我鼓励您考虑这两种方法,对它们进行试验,然后选择最适合您的应用程序和团队的方法。

回答by Sebastien Lorber

Update: React 16.0 introduced portals through ReactDOM.createPortallink

更新:React 16.0 通过链接引入了门户ReactDOM.createPortal

Update: next versions of React (Fiber: probably 16 or 17) will include a method to create portals: ReactDOM.unstable_createPortal()link

更新:React 的下一个版本(Fiber:可能是 16 或 17)将包括创建门户的方法:ReactDOM.unstable_createPortal()链接



Use portals

使用门户

Dan Abramov answer first part is fine, but involves a lot of boilerplate. As he said, you can also use portals. I'll expand a bit on that idea.

Dan Abramov 回答第一部分很好,但涉及很多样板。正如他所说,您也可以使用门户网站。我将扩展一下这个想法。

The advantage of a portal is that the popup and the button remain very close into the React tree, with very simple parent/child communication using props: you can easily handle async actions with portals, or let the parent customize the portal.

门户的优点是弹出窗口和按钮与 React 树保持非常接近,使用 props 进行非常简单的父/子通信:您可以轻松处理门户的异步操作,或者让父级自定义门户。

What is a portal?

什么是门户?

A portal permits you to render directly inside document.bodyan element that is deeply nested in your React tree.

门户允许您直接在document.body深深嵌套在 React 树中的元素内进行渲染。

The idea is that for example you render into body the following React tree:

这个想法是,例如,您将以下 React 树渲染到 body 中:

<div className="layout">
  <div className="outside-portal">
    <Portal>
      <div className="inside-portal">
        PortalContent
      </div>
    </Portal>
  </div>
</div>

And you get as output:

你得到的输出:

<body>
  <div class="layout">
    <div class="outside-portal">
    </div>
  </div>
  <div class="inside-portal">
    PortalContent
  </div>
</body>

The inside-portalnode has been translated inside <body>, instead of its normal, deeply-nested place.

inside-portal节点已被翻译到 内部<body>,而不是其正常的、深嵌套的位置。

When to use a portal

何时使用门户

A portal is particularly helpful for displaying elements that should go on top of your existing React components: popups, dropdowns, suggestions, hotspots

门户对于显示应该放在现有 React 组件之上的元素特别有用:弹出窗口、下拉菜单、建议、热点

Why use a portal

为什么要使用门户

No z-index problems anymore: a portal permits you to render to <body>. If you want to display a popup or dropdown, this is a really nice idea if you don't want to have to fight against z-index problems. The portal elements get added do document.bodyin mount order, which means that unless you play with z-index, the default behavior will be to stack portals on top of each others, in mounting order. In practice, it means that you can safely open a popup from inside another popup, and be sure that the 2nd popup will be displayed on top of the first, without having to even think about z-index.

不再有 z-index 问题:门户允许您渲染到<body>. 如果您想显示弹出窗口或下拉菜单,如果您不想与 z-index 问题作斗争,这是一个非常好的主意。门户元素document.body按安装顺序添加,这意味着除非您使用z-index,否则默认行为将按安装顺序将门户堆叠在彼此的顶部。在实践中,这意味着您可以安全地从另一个弹出窗口内部打开一个弹出窗口,并确保第二个弹出窗口将显示在第一个弹出窗口的顶部,甚至无需考虑z-index.

In practice

在实践中

Most simple: use local React state:if you think, for a simple delete confirmation popup, it's not worth to have the Redux boilerplate, then you can use a portal and it greatly simplifies your code. For such a use case, where the interaction is very local and is actually quite an implementation detail, do you really care about hot-reloading, time-traveling, action logging and all the benefits Redux brings you? Personally, I don't and use local state in this case. The code becomes as simple as:

最简单:使用本地 React 状态:如果您认为,对于简单的删除确认弹出窗口,使用 Redux 样板是不值得的,那么您可以使用门户,它极大地简化了您的代码。对于这样一个用例,交互非常本地化,实际上是一个实现细节,你真的关心热重载、时间旅行、动作日志以及 Redux 给你带来的所有好处吗?就我个人而言,在这种情况下我不使用本地状态。代码变得如此简单:

class DeleteButton extends React.Component {
  static propTypes = {
    onDelete: PropTypes.func.isRequired,
  };

  state = { confirmationPopup: false };

  open = () => {
    this.setState({ confirmationPopup: true });
  };

  close = () => {
    this.setState({ confirmationPopup: false });
  };

  render() {
    return (
      <div className="delete-button">
        <div onClick={() => this.open()}>Delete</div>
        {this.state.confirmationPopup && (
          <Portal>
            <DeleteConfirmationPopup
              onCancel={() => this.close()}
              onConfirm={() => {
                this.close();
                this.props.onDelete();
              }}
            />
          </Portal>
        )}
      </div>
    );
  }
}

Simple: you can still use Redux state: if you really want to, you can still use connectto choose whether or not the DeleteConfirmationPopupis shown or not. As the portal remains deeply nested in your React tree, it is very simple to customize the behavior of this portal because your parent can pass props to the portal. If you don't use portals, you usually have to render your popups at the top of your React tree for z-indexreasons, and usually have to think about things like "how do I customize the generic DeleteConfirmationPopup I built according to the use case". And usually you'll find quite hacky solutions to this problem, like dispatching an action that contains nested confirm/cancel actions, a translation bundle key, or even worse, a render function (or something else unserializable). You don't have to do that with portals, and can just pass regular props, since DeleteConfirmationPopupis just a child of the DeleteButton

简单:您仍然可以使用 Redux 状态:如果您真的想要,您仍然可以使用connect来选择是否DeleteConfirmationPopup显示。由于门户仍然深深嵌套在您的 React 树中,因此自定义此门户的行为非常简单,因为您的父级可以将 props 传递给门户。如果你不使用门户,你通常需要在你的 React 树的顶部渲染你的弹出窗口z-index原因,并且通常必须考虑诸如“如何自定义我根据用例构建的通用 DeleteConfirmationPopup”之类的事情。通常你会发现这个问题的解决方案非常笨拙,比如调度一个包含嵌套确认/取消操作的操作、一个翻译包键,或者更糟糕的是,一个渲染函数(或其他不可序列化的东西)。你不必用传送门做这件事,可以只传递常规道具,因为DeleteConfirmationPopup它只是DeleteButton

Conclusion

结论

Portals are very useful to simplify your code. I couldn't do without them anymore.

门户对于简化代码非常有用。我不能没有他们了。

Note that portal implementations can also help you with other useful features like:

请注意,门户实现还可以帮助您使用其他有用的功能,例如:

  • Accessibility
  • Espace shortcuts to close the portal
  • Handle outside click (close portal or not)
  • Handle link click (close portal or not)
  • React Context made available in portal tree
  • 无障碍
  • 关闭门户的 Espace 快捷方式
  • 处理外部点击(是否关闭门户)
  • 处理链接点击(是否关闭门户)
  • React Context 在门户树中可用

react-portalor react-modalare nice for popups, modals, and overlays that should be full-screen, generally centered in the middle of the screen.

react-portalreact-modal非常适合应该全屏显示的弹出窗口、模式和覆盖,通常位于屏幕中间。

react-tetheris unknown to most React developers, yet it's one of the most useful tools you can find out there. Tetherpermits you to create portals, but will position automatically the portal, relative to a given target. This is perfect for tooltips, dropdowns, hotspots, helpboxes... If you have ever had any problem with position absolute/relativeand z-index, or your dropdown going outside of your viewport, Tether will solve all that for you.

大多数 React 开发人员都不知道react-tether,但它是您可以在那里找到的最有用的工具之一。Tether允许您创建门户,但会相对于给定目标自动定位门户。这非常适用于工具提示、下拉菜单、热点、帮助框……如果您对位置absolute/relativez-index或您的下拉菜单有任何问题,Tether 将为您解决所有问题。

You can, for example, easily implement onboarding hotspots, that expands to a tooltip once clicked:

例如,您可以轻松实现入职热点,点击后会扩展为工具提示:

Onboarding hotspot

入职热点

Real production code here. Can't be any simpler :)

真正的生产代码在这里。不能更简单:)

<MenuHotspots.contacts>
  <ContactButton/>
</MenuHotspots.contacts>


Edit: just discovered react-gatewaywhich permits to render portals into the node of your choice (not necessarily body)

编辑:刚刚发现反应网关,它允许将门户渲染到您选择的节点(不一定是主体)

Edit: it seems react-poppercan be a decent alternative to react-tether. PopperJSis a library that only computes an appropriate position for an element, without touching the DOM directly, letting the user choose where and when he wants to put the DOM node, while Tether appends directly to the body.

编辑:似乎react-popper可以作为 react-tether 的一个不错的替代品。PopperJS是一个库,它只为一个元素计算一个合适的位置,不直接接触 DOM,让用户选择他想要放置 DOM 节点的位置和时间,而 Tether 直接附加到主体。

Edit: there's also react-slot-fillwhich is interesting and can help solve similar problems by allowing to render an element to a reserved element slot that you put anywhere you want in your tree

编辑:还有react-slot-fill这很有趣,可以通过允许将元素渲染到保留的元素槽来帮助解决类似的问题,该槽可以放置在树中的任何位置

回答by fckt

A lot of good solutions and valuable commentaries by known experts from JS community on the topic could be found here. It could be an indicator that it's not that trivial problem as it may seem. I think this is why it could be the source of doubts and uncertainty on the issue.

在这里可以找到很多来自 JS 社区的知名专家关于该主题的很好的解决方案和有价值的评论。这可能是一个指标,表明这不是看起来那么微不足道的问题。我认为这就是为什么它可能成为这个问题的怀疑和不确定性的来源。

Fundamental problem here is that in React you're only allowed to mount component to its parent, which is not always the desired behavior. But how to address this issue?

这里的基本问题是,在 React 中,您只允许将组件挂载到其父级,这并不总是所需的行为。但是如何解决这个问题呢?

I propose the solution, addressed to fix this issue. More detailed problem definition, src and examples can be found here: https://github.com/fckt/react-layer-stack#rationale

我提出了解决方案,旨在解决这个问题。更详细的问题定义、源代码和示例可以在这里找到:https: //github.com/fckt/react-layer-stack#rationale

Rationale

react/react-domcomes comes with 2 basic assumptions/ideas:

  • every UI is hierarchical naturally. This why we have the idea of componentswhich wrap each other
  • react-dommounts (physically) child component to its parent DOM node by default

The problem is that sometimes the second property isn't what you want in your case. Sometimes you want to mount your component into different physical DOM node and hold logical connection between parent and child at the same time.

Canonical example is Tooltip-like component: at some point of development process you could find that you need to add some description for your UI element: it'll render in fixed layer and should know its coordinates (which are that UI elementcoord or mouse coords) and at the same time it needs information whether it needs to be shown right now or not, its content and some context from parent components. This example shows that sometimes logical hierarchy isn't match with the physical DOM hierarchy.

基本原理

react/react-dom带有 2 个基本假设/想法:

  • 每个 UI 都是自然分层的。这就是为什么我们有components相互包裹的想法
  • react-dom默认情况下将子组件(物理上)挂载到其父 DOM 节点

问题是有时第二个属性不是你想要的。有时你想将你的组件挂载到不同的物理 DOM 节点,同时保持父子节点之间的逻辑连接。

典型的例子就是工具提示状的部分:在开发过程中的某些时候,你会发现,你需要添加一些说明对你的UI element:它会在固定层渲染和应该知道它的坐标(这是UI element坐标或鼠标coords)使用,并在同时它需要信息是否需要立即显示,它的内容和来自父组件的一些上下文。此示例显示有时逻辑层次结构与物理 DOM 层次结构不匹配。

Take a look at https://github.com/fckt/react-layer-stack/blob/master/README.md#real-world-usage-exampleto see the concrete example which is answer to your question:

看看https://github.com/fckt/react-layer-stack/blob/master/README.md#real-world-usage-example看看具体的例子,它是你的问题的答案:

import { Layer, LayerContext } from 'react-layer-stack'
// ... for each `object` in array of `objects`
  const modalId = 'DeleteObjectConfirmation' + objects[rowIndex].id
  return (
    <Cell {...props}>
        // the layer definition. The content will show up in the LayerStackMountPoint when `show(modalId)` be fired in LayerContext
        <Layer use={[objects[rowIndex], rowIndex]} id={modalId}> {({
            hideMe, // alias for `hide(modalId)`
            index } // useful to know to set zIndex, for example
            , e) => // access to the arguments (click event data in this example)
          <Modal onClick={ hideMe } zIndex={(index + 1) * 1000}>
            <ConfirmationDialog
              title={ 'Delete' }
              message={ "You're about to delete to " + '"' + objects[rowIndex].name + '"' }
              confirmButton={ <Button type="primary">DELETE</Button> }
              onConfirm={ this.handleDeleteObject.bind(this, objects[rowIndex].name, hideMe) } // hide after confirmation
              close={ hideMe } />
          </Modal> }
        </Layer>

        // this is the toggle for Layer with `id === modalId` can be defined everywhere in the components tree
        <LayerContext id={ modalId }> {({showMe}) => // showMe is alias for `show(modalId)`
          <div style={styles.iconOverlay} onClick={ (e) => showMe(e) }> // additional arguments can be passed (like event)
            <Icon type="trash" />
          </div> }
        </LayerContext>
    </Cell>)
// ...

回答by kskkido

In my opinion the bare minimum implementation has two requirements. A state that keeps track of whether the modal is open or not, and a portal to render the modal outside of the standard react tree.

在我看来,最低限度的实现有两个要求。跟踪模态是否打开的状态,以及在标准反应树之外渲染模态的门户。

The ModalContainer component below implements those requirements along with corresponding render functions for the modal and the trigger, which is responsible for executing the callback to open the modal.

下面的 ModalContainer 组件实现了这些要求以及相应的模态渲染函数和触发器,它负责执行回调以打开模态。

import React from 'react';
import PropTypes from 'prop-types';
import Portal from 'react-portal';

class ModalContainer extends React.Component {
  state = {
    isOpen: false,
  };

  openModal = () => {
    this.setState(() => ({ isOpen: true }));
  }

  closeModal = () => {
    this.setState(() => ({ isOpen: false }));
  }

  renderModal() {
    return (
      this.props.renderModal({
        isOpen: this.state.isOpen,
        closeModal: this.closeModal,
      })
    );
  }

  renderTrigger() {
     return (
       this.props.renderTrigger({
         openModal: this.openModal
       })
     )
  }

  render() {
    return (
      <React.Fragment>
        <Portal>
          {this.renderModal()}
        </Portal>
        {this.renderTrigger()}
      </React.Fragment>
    );
  }
}

ModalContainer.propTypes = {
  renderModal: PropTypes.func.isRequired,
  renderTrigger: PropTypes.func.isRequired,
};

export default ModalContainer;

And here's a simple use case...

这是一个简单的用例......

import React from 'react';
import Modal from 'react-modal';
import Fade from 'components/Animations/Fade';
import ModalContainer from 'components/ModalContainer';

const SimpleModal = ({ isOpen, closeModal }) => (
  <Fade visible={isOpen}> // example use case with animation components
    <Modal>
      <Button onClick={closeModal}>
        close modal
      </Button>
    </Modal>
  </Fade>
);

const SimpleModalButton = ({ openModal }) => (
  <button onClick={openModal}>
    open modal
  </button>
);

const SimpleButtonWithModal = () => (
   <ModalContainer
     renderModal={props => <SimpleModal {...props} />}
     renderTrigger={props => <SimpleModalButton {...props} />}
   />
);

export default SimpleButtonWithModal;

I use render functions, because I want to isolate state management and boilerplate logic from the implementation of the rendered modal and trigger component. This allows the rendered components to be whatever you want them to be. In your case, I suppose the modal component could be a connected component that receives a callback function that dispatches an asynchronous action.

我使用渲染函数,因为我想将状态管理和样板逻辑与渲染的模态和触发器组件的实现隔离开来。这允许渲染的组件成为你想要的任何东西。在您的情况下,我认为模态组件可能是一个连接的组件,它接收一个调度异步操作的回调函数。

If you need to send dynamic props to the modal component from the trigger component, which hopefully doesn't happen too often, I recommend wrapping the ModalContainer with a container component that manages the dynamic props in its own state and enhance the original render methods like so.

如果您需要从触发器组件向模态组件发送动态道具(希望不会经常发生),我建议将 ModalContainer 与一个容器组件包装起来,该容器组件在其自己的状态下管理动态道具并增强原始渲染方法,例如所以。

import React from 'react'
import partialRight from 'lodash/partialRight';
import ModalContainer from 'components/ModalContainer';

class ErrorModalContainer extends React.Component {
  state = { message: '' }

  onError = (message, callback) => {
    this.setState(
      () => ({ message }),
      () => callback && callback()
    );
  }

  renderModal = (props) => (
    this.props.renderModal({
       ...props,
       message: this.state.message,
    })
  )

  renderTrigger = (props) => (
    this.props.renderTrigger({
      openModal: partialRight(this.onError, props.openModal)
    })
  )

  render() {
    return (
      <ModalContainer
        renderModal={this.renderModal}
        renderTrigger={this.renderTrigger}
      />
    )
  }
}

ErrorModalContainer.propTypes = (
  ModalContainer.propTypes
);

export default ErrorModalContainer;

回答by gazdagergo

Wrap the modal into a connected container and perform the async operation in here. This way you can reach both the dispatch to trigger actions and the onClose prop too. To reach dispatchfrom props, do notpass mapDispatchToPropsfunction to connect.

将模态包装到一个连接的容器中并在此处执行异步操作。通过这种方式,您可以同时访问调度以触发操作和 onClose 道具。为了达到dispatch从道具,千万不能传递mapDispatchToProps功能connect

class ModalContainer extends React.Component {
  handleDelete = () => {
    const { dispatch, onClose } = this.props;
    dispatch({type: 'DELETE_POST'});

    someAsyncOperation().then(() => {
      dispatch({type: 'DELETE_POST_SUCCESS'});
      onClose();
    })
  }

  render() {
    const { onClose } = this.props;
    return <Modal onClose={onClose} onSubmit={this.handleDelete} />
  }
}

export default connect(/* no map dispatch to props here! */)(ModalContainer);

The App where the modal is rendered and its visibility state is set:

呈现模态并设置其可见性状态的应用程序:

class App extends React.Component {
  state = {
    isModalOpen: false
  }

  handleModalClose = () => this.setState({ isModalOpen: false });

  ...

  render(){
    return (
      ...
      <ModalContainer onClose={this.handleModalClose} />  
      ...
    )
  }

}