如何用转换创build一个React模态(附加到`<body>`)?

在这个答案中有一个模式,通过将它附加到<body>来创build一个基于React的模态。 但是,我发现它与React提供的过渡插件不兼容。

如何创build一个与转换(在进入和离开)?

在反应conf 2015年,瑞安佛罗伦萨展示使用门户网站 。 以下是如何创build一个简单的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演示

你也可以看看瑞恩的反应模式 ,虽然我没有实际使用它,所以我不知道它与animation效果如何。

我写了模块反应门户 ,应该帮助你。

React 16现在有门户内置。 你可能应该使用它 。

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

我已经提出了解决scheme,解决这个问题。 更详细的问题定义,src和例子可以在这里find: https : //github.com/fckt/react-layer-stack#rationale

合理

react / react-dom来与2个基本的假设/想法:

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

问题是,有时第二个属性不是你想要在你的情况下。 有时候你想要把你的组件挂载到不同的物理DOM节点上,并同时保持父子之间的逻辑连接。

规范的例子是类似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>) // ... 

我已经写了一个图书馆来帮助这个。 我避免了门户策略所使用的DOM插入攻击,而是利用基于上下文的注册中心将组件从源传递到目标。

我的实现使用了标准的React渲染周期。 传送/注入/传送的组件不会在目标上产生双重渲染周期 – 所有事件都会同步发生。

API的结构也是为了阻止在代码中使用魔术string来定义源/目标。 相反,您需要显式创build和修饰将用作目标(可注入)和源(注入器)的组件。 由于这种事情通常被认为是相当神奇的,所以我认为明确的组件表示(需要直接导入和使用)可能有助于减轻对组件注入的混淆。

尽pipe我的库不允许将其作为document.body的直接子对象进行渲染,但是通过绑定到组件树中的根级别组件,您可以获得可接受的模式效果。 我打算很快添加一个这个用例的例子。

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

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

在这里检查工作示例。

希望能帮助到你。 这是我目前实现的一个转换模式的基础上面的anserser:

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