Подтвердить что ты не робот

Как создать модификатор React Modal (который добавляется к `<body>`) с переходами?

В этом ответе есть модальный qaru.site/info/152308/..., который создает модификацию на основе React, добавляя ее к <body>. Однако я обнаружил, что он несовместим с переходными аддонами, предоставляемыми React.

Как создать один с переходами (во время входа и выхода)?

4b9b3361

Ответ 1

В реале 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

Вы также можете взглянуть на Ryan react-modal, хотя я на самом деле не использовал его, поэтому я не знаю, насколько хорошо он работает с анимацией.

Ответ 2

Я написал модуль react-portal, который должен вам помочь.

Ответ 3

Теперь у React 16 есть portals встроенный. Вероятно, вы должны использовать .

Здесь версия 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>

Ответ 4

Основная проблема здесь заключается в том, что в React вам разрешено монтировать компонент только его родительскому элементу, что не всегда является желаемым поведением. Но как решить эту проблему?

Я сделал решение, адресованное для исправления этой проблемы. Более подробное определение проблемы, src и примеры можно найти здесь: https://github.com/fckt/react-layer-stack#rationale

Обоснование

react/react-dom поставляется с двумя основными предположениями/идеями:

  • каждый пользовательский интерфейс является естественным. Вот почему мы имеем идею components, которые обертывают друг друга
  • react-dom по умолчанию монтирует (физически) дочерний компонент в родительский DOM node

Проблема в том, что иногда второе свойство не то, что вы хотите в твоем случае. Иногда вы хотите подключить свой компонент в различные физические DOM node и провести логическое соединение между родитель и ребенок одновременно.

Канонический пример - это компонент, подобный подсказке: в какой-то момент процесс разработки, вы можете обнаружить, что вам нужно добавить некоторые описание для вашего 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>)
// ...

Ответ 5

Я написал библиотеку, чтобы помочь с этим. Я избегаю хакеров ввода DOM, используемых стратегиями портала, и вместо этого использую реестры на основе контекста для передачи компонентов от источника к цели.

В моей реализации используются стандартные циклы рендеринга React. Компоненты, которые вы телепортируете/вставляете/переносите, не вызывают двойного цикла рендеринга на цели - все происходит синхронно.

API также структурирован таким образом, чтобы препятствовать использованию магических строк в вашем коде для определения источника/цели. Вместо этого вам необходимо явно создавать и украшать компоненты, которые будут использоваться в качестве цели (Injectable) и источника (Injector). Поскольку такого рода вещи обычно считаются довольно волшебными, я думаю, что явное представление Компонента (требующее прямого импорта и использования) может помочь облегчить путаницу в том, где вводится Компонент.

Хотя моя библиотека не позволит вам отображать как прямой дочерний объект document.body, вы можете добиться приемлемого модального эффекта, привязавшись к компоненту корневого уровня в своем дереве компонентов. В ближайшее время я планирую добавить пример этого варианта использования.

Подробнее см. https://github.com/ctrlplusb/react-injectables.

Ответ 6

Как утверждают другие ответы, это можно сделать с помощью порталов. Начиная с v16.0 Portals включены в React.

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

Обычно, когда вы возвращаете элемент из метода рендеринга компонента, он монтируется в DOM как дочерний элемент ближайшего родителя node, но с порталами вы можете вставить ребенка в другое место в 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"));

Проверьте рабочий пример здесь.

Ответ 7

Надеюсь, это поможет. Это моя текущая реализация модальности перехода на основе вышеперечисленного:

  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()