react-bootstrap 卸载父元素时 ModalTrigger 不会隐藏
react-bootstrap ModalTrigger doesn't hide when the parent element is unmounted
我们在将 react-bootstrap 的 ModalTrigger 与项目数组一起使用时遇到了一个奇怪的行为,因为当 parent/owner 项目被卸载时它不会消失。我们怀疑这与 React 的虚拟 DOM 和差异机制有关,and/or 我们自己对 ModalTrigger 的误用。
设置很简单:Content React 组件有一个状态,其中包含一个项目名称数组。它还具有一个 onClick(name) 函数,可通过 setState 从数组中删除该名称。在渲染中,它使用 _.map 创建一堆 Item 反应组件。
每个 Item 组件都显示其名称和一个包含标记为 "delete me" 的按钮的 ModalTrigger。单击按钮,它会打开模态;单击模态中的 "OK" 并执行对 Content remove 函数的回调。
当删除最后一个项目时,它工作正常:卸载最终的 Item 组件,以及 ModalTrigger 及其对应的模态。
我们看到的有问题的行为是在删除除最后一项以外的任何项目时。该项目已删除,但模态保持打开状态,而我天真地希望模态消失,因为父 ModalTrigger 消失了。不仅如此,再次点击"ok"时,列表中的下一项被移除,以此类推,直到模态恰好与最后一项相关联,此时点击"ok"最终将隐藏它。
我们的集体预感是,这是由于overlayMixin 的_overlayTarget 是文档中的匿名元素造成的,因此不同的ModalTriggers 无法区分它们。因此,当父级卸载并且 React 查找 DOM diff 时,它会看到前一个触发器并说 "hey, that could work".
整个问题可以通过在 Item 的内部 _onClick() 函数中添加一个 hide() 调用来轻松解决,正如代码中注释掉的那样,我们终于得出了我的问题:
我是否正确使用了 ModalTrigger,因为它期望它在父级卸载时消失?这就是我期望 React 一般工作的方式,这意味着 react-bootstrap 中的错误。
或者我应该显式调用 hide() 因为这是他们设计这个组件的方式吗?
以下是重现此内容的一段代码。
谢谢!
var DeleteModal = React.createClass({
render:function() {
return (
<ReactBootstrap.Modal onRequestHide = {this.props.onRequestHide} title = "delete this?">
<div className="modal-body">
Are you sure?
</div>
<div className="modal-footer">
<button onClick={this.props.onOkClick}>ok</button>
<button onClick={this.props.onRequestHide}>cancel</button>
</div>
</ReactBootstrap.Modal>
);
}
});
var Item = React.createClass({
_onClick:function() {
//this.refs.theTrigger.hide();
this.props.onClick(this.props.name);
},
render:function() {
return (
<div>
<span>{this.props.name}</span>
<ModalTrigger modal={<DeleteModal onOkClick={this._onClick}/>} ref="theTrigger">
<button>delete me!</button>
</ModalTrigger>
</div>
);
}
});
var Content = React.createClass({
onClick:function(name) {
this.setState({items:_.reject(this.state.items, function(item) {return item === name;})});
},
getInitialState:function() {
return {items : ["first", "secondth", "thirdst"]};
},
render:function() {
return (
<div>
{_.map(this.state.items, function(item, i) {
return (
<Item name={item} onClick={this.onClick} key={i}/>
)}.bind(this)
)}
</div>
);
}
});
React.render(<Content/>, document.getElementById("mydiv"));
原来是误用了 React 的 "key" 属性。我们给了映射对象整数键,所以当再次调用渲染时,给出了相同的初始键,这就是为什么 React 认为它应该重用相同的 DOM 元素。
如果我们给它 key={item} (其中 item 是一个简单的字符串),它就解决了我们的问题;然而,这引入了一个微妙的错误,如果有 2 个相同的字符串,React 将只显示一个。
试图通过给它 key={item + i} 来打败它会引入一个更微妙的错误,其中显示重复的项目但被大量删除,但在这种情况下,错误出现在 onClick 方法中,它需要进行修改以接受某种索引。
因此我的结论是键必须是唯一的字符串,回调在执行任何修改时都应考虑这些键。
我们在将 react-bootstrap 的 ModalTrigger 与项目数组一起使用时遇到了一个奇怪的行为,因为当 parent/owner 项目被卸载时它不会消失。我们怀疑这与 React 的虚拟 DOM 和差异机制有关,and/or 我们自己对 ModalTrigger 的误用。
设置很简单:Content React 组件有一个状态,其中包含一个项目名称数组。它还具有一个 onClick(name) 函数,可通过 setState 从数组中删除该名称。在渲染中,它使用 _.map 创建一堆 Item 反应组件。
每个 Item 组件都显示其名称和一个包含标记为 "delete me" 的按钮的 ModalTrigger。单击按钮,它会打开模态;单击模态中的 "OK" 并执行对 Content remove 函数的回调。
当删除最后一个项目时,它工作正常:卸载最终的 Item 组件,以及 ModalTrigger 及其对应的模态。
我们看到的有问题的行为是在删除除最后一项以外的任何项目时。该项目已删除,但模态保持打开状态,而我天真地希望模态消失,因为父 ModalTrigger 消失了。不仅如此,再次点击"ok"时,列表中的下一项被移除,以此类推,直到模态恰好与最后一项相关联,此时点击"ok"最终将隐藏它。
我们的集体预感是,这是由于overlayMixin 的_overlayTarget 是文档中的匿名元素造成的,因此不同的ModalTriggers 无法区分它们。因此,当父级卸载并且 React 查找 DOM diff 时,它会看到前一个触发器并说 "hey, that could work".
整个问题可以通过在 Item 的内部 _onClick() 函数中添加一个 hide() 调用来轻松解决,正如代码中注释掉的那样,我们终于得出了我的问题:
我是否正确使用了 ModalTrigger,因为它期望它在父级卸载时消失?这就是我期望 React 一般工作的方式,这意味着 react-bootstrap 中的错误。
或者我应该显式调用 hide() 因为这是他们设计这个组件的方式吗?
以下是重现此内容的一段代码。
谢谢!
var DeleteModal = React.createClass({
render:function() {
return (
<ReactBootstrap.Modal onRequestHide = {this.props.onRequestHide} title = "delete this?">
<div className="modal-body">
Are you sure?
</div>
<div className="modal-footer">
<button onClick={this.props.onOkClick}>ok</button>
<button onClick={this.props.onRequestHide}>cancel</button>
</div>
</ReactBootstrap.Modal>
);
}
});
var Item = React.createClass({
_onClick:function() {
//this.refs.theTrigger.hide();
this.props.onClick(this.props.name);
},
render:function() {
return (
<div>
<span>{this.props.name}</span>
<ModalTrigger modal={<DeleteModal onOkClick={this._onClick}/>} ref="theTrigger">
<button>delete me!</button>
</ModalTrigger>
</div>
);
}
});
var Content = React.createClass({
onClick:function(name) {
this.setState({items:_.reject(this.state.items, function(item) {return item === name;})});
},
getInitialState:function() {
return {items : ["first", "secondth", "thirdst"]};
},
render:function() {
return (
<div>
{_.map(this.state.items, function(item, i) {
return (
<Item name={item} onClick={this.onClick} key={i}/>
)}.bind(this)
)}
</div>
);
}
});
React.render(<Content/>, document.getElementById("mydiv"));
原来是误用了 React 的 "key" 属性。我们给了映射对象整数键,所以当再次调用渲染时,给出了相同的初始键,这就是为什么 React 认为它应该重用相同的 DOM 元素。
如果我们给它 key={item} (其中 item 是一个简单的字符串),它就解决了我们的问题;然而,这引入了一个微妙的错误,如果有 2 个相同的字符串,React 将只显示一个。
试图通过给它 key={item + i} 来打败它会引入一个更微妙的错误,其中显示重复的项目但被大量删除,但在这种情况下,错误出现在 onClick 方法中,它需要进行修改以接受某种索引。
因此我的结论是键必须是唯一的字符串,回调在执行任何修改时都应考虑这些键。