Javascript 如何创建带有转换的 React Modal(附加到 `<body>`)?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/28802179/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to create a React Modal(which is append to `<body>`) with transitions?
提问by jiyinyiyong
There is a modal in this answer https://stackoverflow.com/a/26789089/883571which is creating a React-based Modal by appending it to <body>. However, I found it not compatible with the transition addons provided by React.
这个答案中有一个模态https://stackoverflow.com/a/26789089/883571它通过将其附加到<body>. 但是,我发现它与 React 提供的转换插件不兼容。
How to create one with transitions(during enter and leave)?
如何创建一个过渡(在进入和离开期间)?
回答by Gil Birman
At react conf 2015, Ryan Florence demonstrated using portals. Here's how you can create a simple Portalcomponent...
在 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);
}
});
and then everything you can normally do in React you can do inside of the portal...
然后你通常可以在 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
jsbin demo
You can also have a look at Ryan's react-modal, although I haven't actually used it so I don't know how well it works with animation.
你也可以看看 Ryan 的react-modal,虽然我还没有真正使用过它,所以我不知道它在动画中的效果如何。
回答by tajo
I wrote the module react-portalthat should help you.
我编写了应该对您有所帮助的模块react-portal。
回答by mpen
React 15.x
反应 15.x
Here's an ES6 version of the method described in this article:
这是本文中描述的方法的 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;
}
}
Just wrap any elements you want to be at the end of the DOM with it:
只需用它包装你想要在 DOM 末尾的任何元素:
<BodyEnd><Tooltip pos={{x,y}}>{content}</Tooltip></BodyEnd>
React 16.x
反应 16.x
Here's an updated version for React 16:
这是 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,
);
}
}
回答by Kristaps Taube
As other answers have stated this can be done using Portals. Starting from v16.0Portalsare included in React.
正如其他答案所述,这可以使用门户来完成。从v16.0门户开始包含在 React 中。
<body>
<div id="root"></div>
<div id="portal"></div>
</body>
Normally, when you return an element from a component's render method, it's mounted into the DOM as a child of the nearest parent node, but with portals you can insert a child into a different location in the DOM.
通常,当您从组件的 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"));
Check working example here.
在此处检查工作示例。
回答by fckt
The fundamental problem here is that in React you're only allowed to mount component to its parent, which is not always the desired behavior. But how to address this issue?
这里的基本问题是,在 React 中,您只允许将组件挂载到其父级,这并不总是所需的行为。但是如何解决这个问题呢?
I've made the solution, addressed to fix this issue. More detailed problem definition, src and examples can be found here: https://github.com/fckt/react-layer-stack#rationale
我已经制定了解决方案,旨在解决此问题。更详细的问题定义、源代码和示例可以在这里找到:https: //github.com/fckt/react-layer-stack#rationale
Rationale
react/react-domcomes comes with 2 basic assumptions/ideas:
- every UI is hierarchical naturally. This why we have the idea of
componentswhich wrap each otherreact-dommounts (physically) child component to its parent DOM node by defaultThe problem is that sometimes the second property isn't what you want in your case. Sometimes you want to mount your component into different physical DOM node and hold logical connection between parent and child at the same time.
Canonical example is Tooltip-like component: at some point of development process you could find that you need to add some description for your
UI element: it'll render in fixed layer and should know its coordinates (which are thatUI elementcoord or mouse coords) and at the same time it needs information whether it needs to be shown right now or not, its content and some context from parent components. This example shows that sometimes logical hierarchy isn't match with the physical DOM hierarchy.
基本原理
react/react-dom带有 2 个基本假设/想法:
- 每个 UI 都是自然分层的。这就是为什么我们有
components相互包裹的想法react-dom默认情况下将子组件(物理上)挂载到其父 DOM 节点问题是有时第二个属性不是你想要的。有时你想将你的组件挂载到不同的物理 DOM 节点,同时保持父子节点之间的逻辑连接。
典型的例子就是工具提示状的部分:在开发过程中的某些时候,你会发现,你需要添加一些说明对你的
UI element:它会在固定层渲染和应该知道它的坐标(这是UI element坐标或鼠标coords)使用,并在同时它需要信息是否需要立即显示,它的内容和来自父组件的一些上下文。此示例显示有时逻辑层次结构与物理 DOM 层次结构不匹配。
Take a look at https://github.com/fckt/react-layer-stack/blob/master/README.md#real-world-usage-exampleto see the concrete example which is answer to your question:
看看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>)
// ...
回答by ctrlplusb
I've written a library to help with this. I avoid the DOM insertion hacks used by Portal strategies out there and instead make use of context based registries to pass along components from a source to a target.
我写了一个库来帮助解决这个问题。我避免了 Portal 策略使用的 DOM 插入技巧,而是使用基于上下文的注册表将组件从源传递到目标。
My implementation makes use of the standard React render cycles. The components that you teleport/inject/transport don't cause a double render cycle on the target - everything happens synchronously.
我的实现使用了标准的 React 渲染周期。您传送/注入/传输的组件不会在目标上造成双重渲染周期 - 一切都是同步发生的。
The API is also structured in a manner to discourage the use of magic strings in your code to define the source/target. Instead you are required to explicitly create and decorate components that will be used as the target (Injectable) and the source (Injector). As this sort of thing is generally considered quite magical I think explicit Component representation (requiring direct imports and usage) may help alleviate confusion on where a Component is being injected.
API 的结构也阻止了在代码中使用魔法字符串来定义源/目标。相反,您需要显式地创建和装饰将用作目标 (Injectable) 和源 (Injector) 的组件。由于这种事情通常被认为是非常神奇的,我认为显式的 Component 表示(需要直接导入和使用)可能有助于减轻对 Component 注入位置的混淆。
Although my library won't allow you to render as a direct child of the document.body you can achieve an acceptable modal effect by binding to a root level component in your component tree. I plan on adding an example of this use case soon.
尽管我的库不允许您作为 document.body 的直接子项进行渲染,但您可以通过绑定到组件树中的根级组件来实现可接受的模态效果。我计划很快添加此用例的示例。
See https://github.com/ctrlplusb/react-injectablesfor more info.
回答by Itay Grudev
I think this code is more or less self explanatory and covers the core solution of what most people are looking for:
我认为这段代码或多或少是不言自明的,涵盖了大多数人正在寻找的核心解决方案:
ReactDOM.render(
<Modal />,
document.body.appendChild( document.createElement( 'div' ) ),
)
回答by jiyinyiyong
Hope it helps. This is my current implementation of a transition modal based on the anwser above:
希望能帮助到你。这是我当前基于上述 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()

