web-dev-qa-db-ja.com

トランジションでReact Modal( `<body>`に追加))を作成するには?

この答えにはモーダルがあります https://stackoverflow.com/a/26789089/883571<body>に追加することでReactベースのモーダルを作成しています。ただし、Reactが提供する移行アドオンと互換性がないことがわかりました。

トランジション(入出中)で作成する方法は?

47
jiyinyiyong

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

また、ライアンの react-modal を見ることができますが、実際には使用していませんので、アニメーションでどのように機能するかわかりません。

61
Gil Birman

モジュールを作成しました react-portal これが役立つはずです。

30
tajo

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

実施例

21
mpen

他の回答が述べているように、これはポータルを使用して行うことができます。から始まる 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"));

動作例を確認してください here

9
Kristaps Taube

ここでの根本的な問題は、Reactでは、コンポーネントをその親にマウントすることしか許可されていないことです。これは、常に望ましい動作ではありません。

この問題を解決するための解決策を作成しました。より詳細な問題定義、src、および例は、ここにあります: https://github.com/fckt/react-layer-stack#rationale

根拠

react/react-domには、2つの基本的な仮定/アイデアがあります。

  • すべてのUIは自然に階層的です。これが、互いにラップするcomponentsのアイデアがある理由です
  • react-domデフォルトで親DOMノードに子コンポーネントを(物理的に)マウントします

問題は、場合によっては2番目のプロパティが必要なものではないことです。コンポーネントを異なる物理DOMノードにマウントし、同時に親子間の論理接続を保持したい場合があります。

標準的な例は、ツールチップのようなコンポーネントです。開発プロセスのある時点で、UI element:固定レイヤーでレンダリングし、その座標を知る必要があります(これはUI element coordまたはmouse coords)および同時に表示する必要があるかどうかの情報、その内容、および親コンポーネントのコンテキストが必要です。この例は、論理階層が物理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>)
// ...
3
fckt

これを支援するライブラリを作成しました。 Portal戦略で使用されるDOM挿入ハッキングを回避し、代わりにコンテキストベースのレジストリを使用して、ソースからターゲットにコンポーネントを渡します。

私の実装では、標準のReactレンダリングサイクルを使用します。テレポート/インジェクト/トランスポートするコンポーネントは、ターゲットでダブルレンダリングサイクルを発生させません。すべてが同期的に発生します。

APIは、ソース/ターゲットを定義するためにコード内でマジックストリングを使用しないように構成されています。代わりに、ターゲット(Injectable)およびソース(Injector)として使用されるコンポーネントを明示的に作成および装飾する必要があります。この種のことは一般的に非常に魔法的であると考えられているため、明示的なコンポーネント表現(直接インポートと使用が必要)は、コンポーネントの注入先に関する混乱を軽減するのに役立つと思います。

私のライブラリでは、document.bodyの直接の子としてレンダリングすることはできませんが、コンポーネントツリーのルートレベルコンポーネントにバインドすることにより、許容できるモーダル効果を実現できます。このユースケースの例をすぐに追加する予定です。

詳細については、 https://github.com/ctrlplusb/react-injectables を参照してください。

2
ctrlplusb

このコードは多かれ少なかれ自明であり、ほとんどの人が探している中核的なソリューションをカバーしていると思います:

ReactDOM.render(
  <Modal />,
  document.body.appendChild( document.createElement( 'div' ) ),
)
1
Itay Grudev

それが役に立てば幸い。これは、上記の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()
0
jiyinyiyong