javascript-如何创建带有过渡的React Modal(追加到<body>之后)?

此答案中有一个模式[https://stackoverflow.com/a/26789089/883571],它通过将其附加到<body>来创建基于React的模式。但是,我发现它与React提供的过渡插件不兼容。 。

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

8个解决方案
62 votes

在2015年的React Conf上,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演示

您也可以看看Ryan的react-modal,尽管我实际上并未使用它,所以我不知道它在动画中的效果如何。

Gil Birman answered 2020-02-11T06:42:40Z
30 votes

我编写了对您有帮助的模块react-portal。

tajo answered 2020-02-11T06:43:01Z
22 votes

反应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>

反应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,
        );
    }
}

工作实例

mpen answered 2020-02-11T06:43:43Z
9 votes

如其他答案所述,可以使用门户网站来完成。 从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"));

在此处检查工作示例。

Kristaps Taube answered 2020-02-11T06:44:12Z
3 votes

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

我已提出解决方案,旨在解决此问题。 可以在这里找到更详细的问题定义,src和示例:[https://github.com/fckt/react-layer-stack#rationale]

基本原理

UI element/UI element随附2个基本假设/想法:

  • 每个UI自然都是分层的。 这就是为什么我们有UI element彼此包裹的想法的原因
  • UI element默认情况下将子组件(物理地)安装到其父DOM节点

问题是有时第二个属性不是您想要的   在你的情况下。 有时您想将组件安装到   不同的物理DOM节点之间并保持逻辑连接   父母和孩子在同一时间。

典型的例子是类似Tooltip的组件:   开发过程中您可能会发现需要添加一些   您的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>)
// ...
fckt answered 2020-02-11T06:45:10Z
2 votes

我已经写了一个库来帮助解决这个问题。 我避免了Portal策略使用的DOM插入攻击,而是利用基于上下文的注册表将组件从源传递到目标。

我的实现使用了标准的React渲染周期。 传送/插入/传送的组件不会在目标上造成双重渲染周期-一切都是同步发生的。

API的结构还不鼓励在代码中使用魔术字符串来定义源/目标。 相反,您需要显式创建和装饰将用作目标(Injectable)和源(Injector)的组件。 由于这种事情通常被认为是非常神奇的,我认为显式的组件表示(需要直接导入和使用)可能有助于减轻对组件注入位置的困惑。

尽管我的库不允许您将其渲染为document.body的直接子级,但您可以通过绑定到组件树中的根级组件来实现可接受的模态效果。 我计划很快添加此用例的示例。

有关更多信息,请参见[https://github.com/ctrlplusb/react-injectables]。

ctrlplusb answered 2020-02-11T06:45:50Z
1 votes

我认为这段代码或多或少是自我解释的,涵盖了大多数人正在寻找的核心解决方案:

ReactDOM.render(
  <Modal />,
  document.body.appendChild( document.createElement( 'div' ) ),
)
Itay Grudev answered 2020-02-11T06:46:10Z
0 votes

希望能帮助到你。 这是我当前基于上述anwser的过渡模式的实现:

  React = require 'react/addons'

  keyboard = require '../util/keyboard'
  mixinLayered = require '../mixin/layered'

  $ = React.DOM
  T = React.PropTypes
  cx = React.addons.classSet

  module.exports = React.createFactory React.createClass
    displayName: 'body-modal'
    mixins: [mixinLayered]

    propTypes:
      # this components accepts children
      name:             T.string.isRequired
      title:            T.string
      onCloseClick:     T.func.isRequired
      showCornerClose:  T.bool
      show:             T.bool.isRequired

    componentDidMount: ->
      window.addEventListener 'keydown', @onWindowKeydown

    componentWillUnmount: ->
      window.removeEventListener 'keydown', @onWindowKeydown

    onWindowKeydown: (event) ->
      if event.keyCode is keyboard.esc
        @onCloseClick()

    onCloseClick: ->
      @props.onCloseClick()

    onBackdropClick: (event) ->
      unless @props.showCornerClose
        if event.target is event.currentTarget
          @onCloseClick()

    renderLayer: ->
      className = "body-modal is-for-#{@props.name}"
      $.div className: className, onClick: @onBackdropClick,
        if @props.showCornerClose
          $.a className: 'icon icon-remove', onClick: @onCloseClick
        $.div className: 'box',
          if @props.title?
            $.div className: 'title',
              $.span className: 'name', @props.title
              $.span className: 'icon icon-remove', @onCloseClick
          @props.children

    render: ->
      $.div()
jiyinyiyong answered 2020-02-11T06:46:31Z
translate from https://stackoverflow.com:/questions/28802179/how-to-create-a-react-modalwhich-is-append-to-body-with-transitions