如何创建带有转换的 React Modal(附加到 `<body>`)?

IT技术 javascript reactjs modal-dialog css-transitions
2021-04-06 10:44:31

这个答案中有一个模态https://stackoverflow.com/a/26789089/883571它通过将其附加到<body>. 但是,我发现它与 React 提供的转换插件不兼容。

如何创建一个过渡(在进入和离开期间)?

6个回答

在 react conf 2015 上,Ryan Florence演示了使用门户以下是创建简单Portal组件的方法...

var Portal = React.createClass({
  render: () => null,
  portalElement: null,
  componentDidMount() {
    var p = this.props.portalId && document.getElementById(this.props.portalId);
    if (!p) {
      var p = document.createElement('div');
      p.id = this.props.portalId;
      document.body.appendChild(p);
    }
    this.portalElement = p;
    this.componentDidUpdate();
  },
  componentWillUnmount() {
    document.body.removeChild(this.portalElement);
  },
  componentDidUpdate() {
    React.render(<div {...this.props}>{this.props.children}</div>, this.portalElement);
  }
});

然后你通常可以在 React 中做的所有事情你都可以在门户内做......

    <Portal className="DialogGroup">
       <ReactCSSTransitionGroup transitionName="Dialog-anim">
         { activeDialog === 1 && 
            <div key="0" className="Dialog">
              This is an animated dialog
            </div> }
       </ReactCSSTransitionGroup>
    </Portal> 

jsbin demo

你也可以看看 Ryan 的react-modal,虽然我还没有真正使用过它,所以我不知道它在动画中的效果如何。

我想我们也需要一个ReactDOM.unmountComponentAtNode(this.portalElement)in componentWillUnmount,不是吗?否则 React 树仍然存在于内存中。它也在react-modalgithub.com/reactjs/react-modal/blob/master/lib/components/... 中
2021-05-27 10:44:31
这是有关该主题的一些进一步阅读另一种选择可能是使用react-motion.
2021-05-30 10:44:31
这是一个不错的解决方案,但如果您使用上下文则不起作用。这是许多库的问题,例如reduxreact-intl
2021-05-31 10:44:31
还有一个独立的组件:github.com/cloudflare/react-gateway
2021-06-02 10:44:31
遇到未捕获的错误,找不到 id 元素...我发现这是因为它使用 React 进行渲染。它应该是 ReactDOM。
2021-06-12 10:44:31

我编写了应该对您有所帮助的modulereact-portal

用法:

import { Portal } from 'react-portal';
 
<Portal>
  This text is portaled at the end of document.body!
</Portal>
 
<Portal node={document && document.getElementById('san-francisco')}>
  This text is portaled into San Francisco!
</Portal>
什么的!伙计,这应该是#1 的答案。
2021-05-31 10:44:31
这是一个了不起的module!!对我来说最好的答案!
2021-06-21 10:44:31

react 15.x

这是本文中描述的方法的 ES6 版本

import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';

export default class BodyEnd extends React.PureComponent {
    
    static propTypes = {
        children: PropTypes.node,
    };
    
    componentDidMount() {
        this._popup = document.createElement('div');
        document.body.appendChild(this._popup);
        this._render();
    }

    componentDidUpdate() {
        this._render();
    }

    componentWillUnmount() {
        ReactDOM.unmountComponentAtNode(this._popup);
        document.body.removeChild(this._popup);
    }

    _render() {
        ReactDOM.render(this.props.children, this._popup);
    }
    
    render() {
        return null;
    }
}

只需用它包装任何你想放在 DOM 末尾的元素:

<BodyEnd><Tooltip pos={{x,y}}>{content}</Tooltip></BodyEnd>

react 16.x

这是 React 16 的更新版本:

import React from 'react';
import ReactDOM from 'react-dom';

export default class BodyEnd extends React.Component {

    constructor(props) {
        super(props);
        this.el = document.createElement('div');
        this.el.style.display = 'contents';
        // The <div> is a necessary container for our
        // content, but it should not affect our layout.
        // Only works in some browsers, but generally
        // doesn't matter since this is at
        // the end anyway. Feel free to delete this line.
    }
    
    componentDidMount() {
        document.body.appendChild(this.el);
    }

    componentWillUnmount() {
        document.body.removeChild(this.el);
    }
    
    render() {
        return ReactDOM.createPortal(
            this.props.children,
            this.el,
        );
    }
}

工作示例

@OZZIE 哈哈.. 一旦你有一些好的功能/组件来使用它们,它们就不会那么糟糕了。我更新了 React 16 的代码。
2021-06-07 10:44:31
感谢 mpen 以上是迄今为止最好和最简单的方法,并且适用于 Cordova
2021-06-10 10:44:31
@mpen 我认为它们的用户体验很差,除了一些罕见的提示,比如“你确定......?”
2021-06-12 10:44:31
@mpen 啊,是的,我在发布评论后意识到该链接指向另一个解决方案,我虽然您发布的代码与此相关:) 如果可行的话会很棒,尽管我有点喜欢能够声称React 并没有很好地支持模态,但现在它们是 :< 我讨厌模态/弹出窗口 :)
2021-06-16 10:44:31
@OZZIE 尝试ReactDOM.createPortal改用。我打赌这会奏效!(对不起,我现在没有发布完整的代码)
2021-06-18 10:44:31

正如其他答案所述,这可以使用门户来完成。v16.0 门户开始包含在 React 中。

<body>
  <div id="root"></div>
  <div id="portal"></div>
</body>

通常,当您从组件的 render 方法返回一个元素时,它会作为最近父节点的子节点挂载到 DOM 中,但是通过门户,您可以将子节点插入到 DOM 中的不同位置。

const PortalComponent = ({ children, onClose }) => {
  return createPortal(
    <div className="modal" style={modalStyle} onClick={onClose}>
      {children}
    </div>,
    // get outer DOM element
    document.getElementById("portal")
  );
};

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      modalOpen: false
    };
  }

  render() {
    return (
      <div style={styles}>
        <Hello name="CodeSandbox" />
        <h2>Start editing to see some magic happen {"\u2728"}</h2>
        <button onClick={() => this.setState({ modalOpen: true })}>
          Open modal
        </button>
        {this.state.modalOpen && (
          <PortalComponent onClose={() => this.setState({ modalOpen: false })}>
            <h1>This is modal content</h1>
          </PortalComponent>
        )}
      </div>
    );
  }
}

render(<App />, document.getElementById("root"));

此处检查工作示例

截至 2018 年 1 月的最新答案......一直到这里......让你想知道这个重复的问题是否是 SO 应该考虑的事情
2021-05-22 10:44:31

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

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

基本原理

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

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

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

规范示例是类似工具提示的组件:在开发过程的某个时刻,您可能会发现您需要为您添加一些描述UI element:它将在固定层中呈现并且应该知道其坐标(即UI element坐标或鼠标坐标)和同时它需要信息是否需要立即显示,它的内容和来自父组件的一些上下文。此示例显示有时逻辑层次结构与物理 DOM 层次结构不匹配。

看看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>)
// ...
不,这还不够。您正在宣传您自己的图书馆作为建议的解决方案。你在很多地方都这样做了。我们的社区认为这种未经披露的促销是一种炒作
2021-05-25 10:44:31
“我提出了解决这个问题的解决方案”——这还不够吗?这不仅与库有关,而且与模式有关,在这种情况下,库是互补的
2021-05-26 10:44:31
顺便说一句,我读到了您共享的链接,所有陈述都非常清晰、公平且符合 IMO 逻辑。我也很抱歉提出这样的话题,不确定这是一个好地方..
2021-06-07 10:44:31
好的,我们可以先同意,这不是明确的,几乎不主观的,有争议的话题吗?如果有人代替我写答案和解释怎么办?它将如何改变话语?
2021-06-17 10:44:31
请明确说明这是您自己的库,并请阅读如何提供个人开源库?
2021-06-20 10:44:31