将组件用作 prop 时,React 上下文不会传输
React context doesn't transfer when using a component as prop
我正在使用 react-bootstrap 的 ModalTrigger 来显示一个字段密集的模态(基于 react-bootstrap 的模态),这意味着向它发送一堆道具:
<ModalTrigger modal={<MyModal field1={value1} field2={value2} (more fields...)/>}>
Click here to open
</ModalTrigger>
创建触发器的父组件通过 props 传入 fields/values,that 组件的父组件也将其作为 props 传递,通过实际保存数据的顶级组件。两者基本上都是管道,这是一个经典的 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>
)
}
});
模态弹出 "The context is",但不显示实际上下文。
我相信这是因为发送到 ModalTrigger 的道具已经 rendered/mounted 不知何故,但我不确定为什么。据我了解,MyModal 的所有者是 Content 组件,这意味着上下文应该没问题,但事实并非如此。
更多信息:我已经尝试将 {...this.props}
和 context={this.context}
传递给 MyModal,但没有成功。此外,可能相关的是,ModalTrigger 使用 cloneElement 来确保模态的 onRequestHide 道具指向触发器的隐藏功能。
那么我在这里缺少什么? :/
React.cloneElement
将在覆盖 ref
属性时更改元素的所有者,这意味着上下文不会从先前的所有者传递。 然而,ModalTrigger
似乎并非如此。
请注意,基于所有者的方法在 React 0.14 中将不再适用,因为上下文将从父级传递给子级,而不是从所有者传递给所有者。 ModalTrigger
在 DOM 的另一个分支中呈现其 modal
节点属性(参见 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()}/>}>
正如 Matt Smith 暗示的那样,事实证明 react-bootstrap 已经包含了一种非常相似的方法来通过 ModalTrigger.withContext
转发上下文。这允许您创建一个 ModalTrigger
组件 class,无论它在 VDOM 树中的位置如何,它都会将其上下文转发到其 modal
节点属性。
// MyModalTrigger.js
module.exports = ModalTrigger.withContext({
foo: React.PropTypes.String
});
有一种更好的方法将上下文传递给 "portal" 类型的组件,这些组件将 children 渲染到 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;
}
};
这是我在 production app Casalova 中使用的一个门户示例,它可以将上下文正确地呈现到他们的 children。
注意:此 API 未记录在案,将来可能会更改。不过现在,这是将上下文呈现到门户组件中的正确方法。
我正在使用 react-bootstrap 的 ModalTrigger 来显示一个字段密集的模态(基于 react-bootstrap 的模态),这意味着向它发送一堆道具:
<ModalTrigger modal={<MyModal field1={value1} field2={value2} (more fields...)/>}>
Click here to open
</ModalTrigger>
创建触发器的父组件通过 props 传入 fields/values,that 组件的父组件也将其作为 props 传递,通过实际保存数据的顶级组件。两者基本上都是管道,这是一个经典的 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>
)
}
});
模态弹出 "The context is",但不显示实际上下文。
我相信这是因为发送到 ModalTrigger 的道具已经 rendered/mounted 不知何故,但我不确定为什么。据我了解,MyModal 的所有者是 Content 组件,这意味着上下文应该没问题,但事实并非如此。
更多信息:我已经尝试将 {...this.props}
和 context={this.context}
传递给 MyModal,但没有成功。此外,可能相关的是,ModalTrigger 使用 cloneElement 来确保模态的 onRequestHide 道具指向触发器的隐藏功能。
那么我在这里缺少什么? :/
React.cloneElement
将在覆盖 ref
属性时更改元素的所有者,这意味着上下文不会从先前的所有者传递。 然而,ModalTrigger
似乎并非如此。
请注意,基于所有者的方法在 React 0.14 中将不再适用,因为上下文将从父级传递给子级,而不是从所有者传递给所有者。 ModalTrigger
在 DOM 的另一个分支中呈现其 modal
节点属性(参见 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()}/>}>
正如 Matt Smith 暗示的那样,事实证明 react-bootstrap 已经包含了一种非常相似的方法来通过 ModalTrigger.withContext
转发上下文。这允许您创建一个 ModalTrigger
组件 class,无论它在 VDOM 树中的位置如何,它都会将其上下文转发到其 modal
节点属性。
// MyModalTrigger.js
module.exports = ModalTrigger.withContext({
foo: React.PropTypes.String
});
有一种更好的方法将上下文传递给 "portal" 类型的组件,这些组件将 children 渲染到 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;
}
};
这是我在 production app Casalova 中使用的一个门户示例,它可以将上下文正确地呈现到他们的 children。
注意:此 API 未记录在案,将来可能会更改。不过现在,这是将上下文呈现到门户组件中的正确方法。