javascript 创建react项目 - 如何使用转换创建一个React Modal(附加到`<body>`)?





react开发 webpack创建react项目 (8)


我写了一个图书馆来帮助解决这个问题。 我避免使用Portal策略所使用的DOM插入黑客,而是使用基于上下文的注册表将组件从源传递到目标。

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

API的结构也可以阻止在代码中使用魔术字符串来定义源/目标。 相反,您需要显式创建和装饰将用作目标(Injectable)和源(Injector)的组件。 由于这类事情通常被认为是非常神奇的,我认为显式的组件表示(需要直接导入和使用)可能有助于缓解组件注入位置的混淆。

虽然我的库不允许您呈现为document.body的直接子项,但您可以通过绑定到组件树中的根级组件来实现可接受的模式效果。 我计划很快添加一个这个用例的例子。

有关详细信息,请参阅https://github.com/ctrlplusb/react-injectables

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

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




React 16现在内置了portals 。 您可能应该使用that

这是本文中描述的方法的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中,您只能将组件安装到其父组件,这并不总是所需的行为。 但是如何解决这个问题呢?

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

合理

react / react-dom有两个基本的假设/想法:

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

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

Canonical示例是类似Tooltip的组件:在开发过程的某个阶段,您可能会发现需要为UI element添加一些描述:它将在固定层中呈现并且应该知道它的坐标(这是UI element坐标或鼠标) 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>)
// ...



在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 ,虽然我还没有真正使用它,所以我不知道它对动画的效果如何。




希望能帮助到你。 这是我目前基于上面的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()



正如其他答案所述,这可以使用门户网站完成。 从v16.0开始, Portals包含在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查看工作示例。




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

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



好的,所以我想我想出了一个超级简单的答案......不max-height,使用relative定位,在li元素上工作,并且是纯CSS。除了Firefox之外,我还没有测试过任何东西,尽管从CSS来看,它应该适用于所有浏览器。

FIDDLE:http://jsfiddle.net/n5XfG/2596/http://jsfiddle.net/n5XfG/2596/

CSS

.wrap { overflow:hidden; }

.inner {
            margin-top:-100%;
    -webkit-transition:margin-top 500ms;
            transition:margin-top 500ms;
}

.inner.open { margin-top:0px; }

HTML

<div class="wrap">
    <div class="inner">Some Cool Content</div>
</div>




javascript reactjs modal-dialog css-transitions