Контекст React не передается при использовании компонента в качестве prop
Я использую ModalTrigger react-bootstrap для показа модального модального поля (на основе модального react-bootstrap), что означает отправку ему кучу реквизита:
<ModalTrigger modal={<MyModal field1={value1} field2={value2} (more fields...)/>}>
Click here to open
</ModalTrigger>
родительский компонент, который создает триггер, имеет поля/значения, переданные через реквизит, и родительский компонент это компонент также прошел как реквизит, компонентом верхнего уровня, который фактически содержит данные. Оба являются в основном трубами, что является классическим сценарием childContext, за исключением что это не работает. Вот упрощенная версия того, что я пробовал:
var MyModal = React.createClass({
contextTypes : {foo : React.PropTypes.string},
render : function() {
return (
<Modal {...this.props} title="MyTitle">
<div className="modal-body">
The context is {this.context.foo}
</div>
</Modal>
);
}
});
var Content = React.createClass({
childContextTypes : {foo: React.PropTypes.string},
getChildContext : function() {return {foo : "bar"}},
render : function() {
return (
<ModalTrigger modal={<MyModal/>}>
<span>Show modal</span>
</ModalTrigger>
)
}
});
модальный всплывает с "контекстом", не показывая фактический контекст.
Я считаю, что это происходит потому, что пропеллер, отправленный в ModalTrigger, уже каким-то образом визуализирован/смонтирован, но я не уверен, почему. Насколько я понимаю, владелец MyModal является компонентом контента, что означает, что контекст должен быть в порядке, но это не так.
некоторые более информация: я уже пробовал пройти {...this.props}
и context={this.context}
в MyModal без успеха. Кроме того, возможно, уместно, ModalTrigger использует cloneElement, чтобы убедиться, что опора onrequesthide модального указывает на функцию скрытия триггера.
так что я упускаю здесь? :/
2 ответов
React.cloneElement
изменит владельца элемента, когда ref
prop переопределен, что означает, что контекст не будет передан от предыдущего владельца. однако, похоже, это не так с ModalTrigger
.
обратите внимание, что подход на основе владельца не будет работать в React 0.14, так как контекст будет передаваться от родителя к ребенку, а не от владельца к владельцу. ModalTrigger
оказывает modal
узел prop в другой ветви DOM (см. OverlayMixin
). Таким образом, ваш Modal
компонент не является ни дочерним, ни потомком вашего Content
компонент и не будет передан дочерний контекст из Content
.
что касается решения вашей проблемы, вы всегда можете создать компонент, единственной целью которого является передача контекста его детям.
var PassContext = React.createClass({
childContextTypes: {
foo: React.PropTypes.string
},
getChildContext: function() {
return this.props.context;
},
render: function() {
return <MyModal />;
},
});
использовать:
<ModalTrigger modal={<PassContext context={this.getChildContext()}/>}>
как намекнул Мэтт Смит, оказывается, что react-bootstrap уже включает в себя очень похожий подход к контексту пересылки via ModalTrigger.withContext
. Это позволяет создать ModalTrigger
класс компонентов, который будет пересылать свой контекст в его modal
node prop, независимо от его положения в дереве VDOM.
// MyModalTrigger.js
module.exports = ModalTrigger.withContext({
foo: React.PropTypes.String
});
существует гораздо лучший способ передачи контекста на компоненты типа "портал", которые отображают своих детей в другой контейнер вне дерева React.
использование" renderSubtreeIntoContainer "вместо" render " также передаст контекст в поддерево.
его можно использовать так:
import React, {PropTypes} from 'react';
import {
unstable_renderSubtreeIntoContainer as renderSubtreeIntoContainer,
unmountComponentAtNode
} from 'react-dom';
export default class extends React.Component {
static displayName = 'ReactPortal';
static propTypes = {
isRendered: PropTypes.bool,
children: PropTypes.node,
portalContainer: PropTypes.node
};
static defaultProps = {
isRendered: true
};
state = {
mountNode: null
};
componentDidMount() {
if (this.props.isRendered) {
this._renderPortal();
}
}
componentDidUpdate(prevProps) {
if (prevProps.isRendered && !this.props.isRendered ||
(prevProps.portalContainer !== this.props.portalContainer &&
prevProps.isRendered)) {
this._unrenderPortal();
}
if (this.props.isRendered) {
this._renderPortal();
}
}
componentWillUnmount() {
this._unrenderPortal();
}
_getMountNode = () => {
if (!this.state.mountNode) {
const portalContainer = this.props.portalContainer || document.body;
const mountNode = document.createElement('div');
portalContainer.appendChild(mountNode);
this.setState({
mountNode
});
return mountNode;
}
return this.state.mountNode;
};
_renderPortal = () => {
const mountNode = this._getMountNode();
renderSubtreeIntoContainer(
this,
(
<div>
{this.props.children}
</div>
),
mountNode,
);
};
_unrenderPortal = () => {
if (this.state.mountNode) {
unmountComponentAtNode(this.state.mountNode);
this.state.mountNode.parentElement.removeChild(this.state.mountNode);
this.setState({
mountNode: null
});
}
};
render() {
return null;
}
};
Это пример портала, который я использую в my производства приложение Casalova которые правильно отображают контекст в их дети.
Примечание: этот API недокументирован и, вероятно, изменится в будущем. На данный момент, однако, это правильный способ визуализации контекста в компоненты портала.