Ответ 1
Изменить 10-25-2018
Весь этот материал стал фактически тривиальным с введением Portals в React 16. Реализация и использование не только намного проще, содержимое iframe также является фактическим потомком "родительского" виртуального домена, что означает общую систему событий, контексты и так далее. Круто, верно?
import React, { Component } from 'react'
import { createPortal } from 'react-dom'
export default class Frame extends Component {
constructor(props) {
super(props)
this.setContentRef = node =>
(this.contentRef =
((!node || !node.contentWindow) && null) ||
node.contentWindow.document.body)
}
render() {
const { children, ...props } = this.props // eslint-disable-line
return (
<iframe {...props} ref={this.setContentRef}>
{this.contentRef &&
createPortal(
React.Children.only(children),
this.contentRef
)}
</iframe>
)
}
}
Это становится еще более лаконичным при использовании React Hooks:
import React, { useState } from 'react'
import { createPortal } from 'react-dom'
export const IFrame = ({ children, ...props }) => {
const [contentRef, setContentRef] = useState(null)
const mountNode = contentRef && contentRef.contentWindow.document.body
return (
<iframe {...props} ref={setContentRef}>
{mountNode &&
createPortal(
React.Children.only(children),
mountNode
)}
</iframe>
)
}
Использование:
import Frame from './Frame'
const MyComp = () => <Frame><h1>Hello Content!</h1></Frame>
Дальнейший контроль, например, над iframes <head>
, может быть легко достигнут, как показано в этом Гисте.
Существует также response-frame-component, пакет, который IMHO предлагает практически все, что вам нужно при работе с фреймами в React.
SSR
Одна вещь, которую (по крайней мере, насколько мне известно) вы вряд ли когда-нибудь получите с Portals, - это рендеринг на стороне сервера, потому что порталы можно рендерить, только когда они имеют ссылку на реальный DOM-узел. Так что если вы...
- ... отчаянно нужно, чтобы содержимое iframe было предварительно обработано
- ... не против вырезать виртуальное дерево
- ... целевые браузеры, которые поддерживают атрибут
srcdoc
в iframes
... тогда вы могли бы начать с чего-то вроде этого:
import React, { Component } from 'react'
import { renderToString } from 'react-dom/server'
import { hydrate, render } from 'react-dom'
const wrapWithMountNode = html => {
return '<!DOCTYPE html><html><head></head><body><div id="frame">${html}</div></body></html>'.trim()
}
export default class SSRFrame extends Component {
constructor(props) {
super(props)
this.initialMarkup = wrapWithMountNode(
renderToString(
React.Children.only(this.props.children)
)
)
this.contentRef = null
this.setContentRef = node => {
this.contentRef =
((!node || !node.contentWindow) && null) ||
node.contentWindow.document.getElementById('frame')
}
}
componentDidMount() {
this.contentRef &&
hydrate(
React.Children.only(this.props.children),
this.contentRef
)
}
componentDidUpdate() {
this.contentRef &&
render(
React.Children.only(this.props.children),
this.contentRef
)
}
componentWillUnmount() {
this.contentRef = null
}
render() {
const { children, ...props } = this.props // eslint-disable-line
return (
<iframe
{...props}
ref={this.setContentRef}
srcDoc={
(!this.contentRef && this.initialMarkup) ||
undefined
}
/>
)
}
}
Счастливого обрамления в 2018 году!
Оригинальный пост
Что касается IFrame, решение на самом деле очень простое: вы должны создать новый рендер DOM для содержимого IFrame и синхронизировать его с остальной частью приложения. Такой компонент может выглядеть примерно так:
var React = require('react');
var ReactDOM = require('react-dom');
var IFrame = React.createClass({
propTypes = {
frameProps: React.PropTypes.object,
},
defaultProps = {
frameProps: {
frameBorder: 0
}
},
updateIFrameContents: function() {
ReactDOM.render((
// Here you put the components that should be in the IFrame
<div {...this.props}>Hello IFrame!</div>
), this.el);
},
render: function() {
return (
<iframe {...this.props.frameProps} />
);
},
componentDidMount: function() {
var frameBody = ReactDOM.findDOMNode(this).contentDocument.body,
el = document.createElement('div');
frameBody.appendChild(el);
this.el = el;
this.updateIFrameContents();
},
componentDidUpdate: function() {
this.updateIFrameContents();
}
});
Это не очень удобно для композиции. Вы не можете использовать React.props.children.only
и лайки, потому что они всегда указывают на уже скомпилированные/созданные элементы, которые уже являются частью дерева различий. И поскольку нам нужно новое дерево различий для содержимого в рамке, вам нужно будет определить новый компонент для каждого содержимого в рамке.
Введите компоненты высшего порядка. Цель состоит в том, чтобы создать вид декоратора, который можно применить к любому элементу, который вы хотите создать:
function FramedComponent(Component) {
return React.createClass({
propTypes = {
frameProps: React.PropTypes.object,
},
defaultProps = {
frameProps: {
frameBorder: 0
}
},
updateIFrameContents: function() {
ReactDOM.render((
<Component {...this.props} />
), this.el);
},
render: function() {
return (
<iframe {...this.props.frameProps} />
);
},
componentDidMount: function() {
var frameBody = ReactDOM.findDOMNode(this).contentDocument.body,
el = document.createElement('div');
frameBody.appendChild(el);
this.el = el;
this.updateIFrameContents();
},
componentDidUpdate: function() {
this.updateIFrameContents();
}
});
}
Используйте как это:
var MyFramedComponent = FramedComponent(MyComponent);
Редактировать 3-7-2017 Одним из главных преимуществ React перед, вероятно, всеми другими в настоящее время модными виртуальными библиотеками DOM, является его синтетическая система событий. Хороший газон, он просто работает и пузырится так очень удобно.
Однако при таком подходе вы (вполне преднамеренно) вырежете одно дерево различий из следующего, и это также верно для системы событий. Для большинства событий, которые не имеют большого значения:
var logNestedClicks = function(event) {
console.log(event);
}
// and then
<MyFramedComponent onClick={logNestedClicks) />
Это будет просто отлично работать. Но есть некоторые не столь заметные исключения, которые, особенно учитывая тот факт, что контролируемые iFrames в React чаще всего используются для простого создания области действия, просто не работают, как ожидалось. Еще один не очень выдающийся пример: onBeforeInsert
. Что делает предварительную проверку экземпляров проекта очень утомительной задачей. И опять же, это, вероятно, не имеет значения для большинства случаев использования. Просто убедитесь, что ваше дерьмо захвачено так, как вы ожидаете, прежде чем делать (WYSIWYG) случай для iFrames в React. Был там, сделал это, поверь мне.