考虑以下典型的React文档结构:
Component.jsx
<OuterClickableArea>
<InnerClickableArea>
content
</InnerClickableArea>
</OuterClickableArea>
这些组件的组成如下:
OuterClickableArea.js
export default class OuterClickableArea extends React.Component {
constructor(props) {
super(props)
this.state = {
clicking: false
}
this.onMouseDown = this.onMouseDown.bind(this)
this.onMouseUp = this.onMouseUp.bind(this)
}
onMouseDown() {
if (!this.state.clicking) {
this.setState({ clicking: true })
}
}
onMouseUp() {
if (this.state.clicking) {
this.setState({ clicking: false })
this.props.onClick && this.props.onClick.apply(this, arguments)
console.log('outer area click')
}
}
render() {
return (
<div
onMouseDown={this.onMouseDown}
onMouseUp={this.onMouseUp}
>
{ this.props.children }
</div>
)
}
}
为了这个参数,除了类名和控制台日志语句之外,InnerClickableArea.js具有几乎相同的代码。
现在,如果您要运行此应用程序并且用户单击内部可点击区域,则会发生以下情况(如预期):
这显示了典型的事件冒泡/传播 - 到目前为止没有任何意外。
将输出:
inner area click
outer area click
现在,如果我们创建的应用程序一次只能进行一次交互,该怎么办?例如,想象一下编辑器,可以通过鼠标点击选择元素。当在内部区域内按下时,我们只想选择内部元素。
简单明了的解决方案是在InnerArea组件中添加一个stopPropagation:
InnerClickableArea.js
onMouseDown(e) {
if (!this.state.clicking) {
e.stopPropagation()
this.setState({ clicking: true })
}
}
这可以按预期工作。它将输出:
inner area click
问题?
InnerClickableArea
特此隐含地选择OuterClickableArea
(和所有其他家长)不能接收活动。即使InnerClickableArea
不应该知道OuterClickableArea
的存在。就像OuterClickableArea
不了解InnerClickableArea
一样(在关注点和可重用性概念分离之后)。我们在OuterClickableArea
&#34;知道&#34;的两个组件之间创建了一个隐式依赖关系。它不会错误地让听众被解雇,因为它会记住&#34; InnerClickableArea将停止任何事件传播。这似乎不对。
我尝试不使用stopPropagation来提高应用程序的可扩展性,因为添加依赖事件的新功能会更容易,而不必记住哪些组件在哪个时间使用stopPropagation
。 / p>
相反,我想使上述逻辑更具说明性,例如:通过在Component.jsx
内以声明方式定义每个区域当前是否可点击。我会让应用状态知道,传递区域是否可点击,并将其重命名为Container.jsx
。像这样:
Container.jsx
<OuterClickableArea
clickable={!this.state.interactionBusy}
onClicking={this.props.setInteractionBusy.bind(this, true)} />
<InnerClickableArea
clickable={!this.state.interactionBusy}
onClicking={this.props.setInteractionBusy.bind(this, true)} />
content
</InnerClickableArea>
</OuterClickableArea>
其中this.props.setInteractionBusy
是一个redux操作,会导致应用状态更新。此外,此容器将app状态映射到其props(未在上面显示),因此将定义this.state.ineractionBusy
。
OuterClickableArea.js (对于InnerClickableArea.js几乎相同)
export default class OuterClickableArea extends React.Component {
constructor(props) {
super(props)
this.state = {
clicking: false
}
this.onMouseDown = this.onMouseDown.bind(this)
this.onMouseUp = this.onMouseUp.bind(this)
}
onMouseDown() {
if (this.props.clickable && !this.state.clicking) {
this.setState({ clicking: true })
this.props.onClicking && this.props.onClicking.apply(this, arguments)
}
}
onMouseUp() {
if (this.state.clicking) {
this.setState({ clicking: false })
this.props.onClick && this.props.onClick.apply(this, arguments)
console.log('outer area click')
}
}
render() {
return (
<div
onMouseDown={this.onMouseDown}
onMouseUp={this.onMouseUp}
>
{ this.props.children }
</div>
)
}
}
问题是Javascript事件循环似乎按以下顺序运行这些操作:
OuterClickableArea
和InnerClickableArea
,其中道具clickable
等于true
(因为app state interactionBusy默认为false)interactionBusy
设置为true
clickable
道具仍为true
,因为即使应用状态interactionBusy
为true
,也会尚未导致重新渲染;渲染操作放在Javascript事件循环结束时)Component.jsx
进行反应,并将clickable
作为false
传递给两个组件,但到现在为止已经太晚了这将输出:
inner area click
outer area click
而期望的输出是:
inner area click
因此,应用程序状态无助于尝试使这些组件更加独立和可重用。原因似乎是即使状态更新,容器也只在事件循环结束时重新渲染,并且鼠标事件传播触发器已在事件循环中排队。
因此,我的问题是:是否有替代方法可以使用app状态以声明方式处理鼠标事件传播,或者是否有更好的方法来实现/执行我的上面用app(redux)状态设置?
答案 0 :(得分:2)
最简单的替代方法是在触发stopPropogation之前添加一个标志,在这种情况下标志是一个参数。
const onMouseDown = (stopPropagation) => {
stopPropagation && event.stopPropagation();
}
现在,甚至应用程序状态或道具甚至可以决定触发停止传播
<div onMouseDown={this.onMouseDown(this.state.stopPropagation)} onMouseUp={this.onMouseUp(this.props.stopPropagation)} >
<InnerComponent stopPropagation = {true}>
</div>
答案 1 :(得分:1)
不是处理组件中的click事件和clickable
状态,而是使用LOCK
状态到Redux状态,当用户单击时,组件只发出CLICK
动作,保持锁定并做逻辑。像这样:
CLICK_LOCK
默认为false
。mouseDown
侦听器触发INNER_MOUSEDOWN
mouseDown
侦听器触发OUTER_MOUSEDOWN
INNER_MOUSEDOWN
操作将CLICK_LOCK
状态设置为true
并执行逻辑(日志inner area click
)。CLICK_LOCK
为true
,OUTER_MOUSEDOWN
不执行任何操作。mouseUp
侦听器触发INNER_MOUSEUP
mouseUp
侦听器触发OUTER_MOUSEUP
INNER_MOUSEUP
操作将CLICK_LOCK
州设置为false
。OUTER_MOUSEUP
操作将CLICK_LOCK
州设置为false
。注意:强>
click
。每个组件都不必了解其他组件,但需要一个全局状态(Redux)来协调。答案 2 :(得分:1)
我怀疑你是要创建像叠加模式这样的东西,当点击外部组件或容器组件时,应该关闭组件,如果是这种情况,你可以使用{{3}如果你有不同的目的,你也可以学习如何处理这种实现。而不是重新创建整个事物。
答案 3 :(得分:0)
如果事件本身已在事件中处理过,您是否传递了信息?
const handleClick = e => {
if (!e.nativeEvent.wasProcessed) {
// do something
e.nativeEvent.wasProcessed = true;
}
};
这样,外部组件可以检查事件是否已被其中一个孩子处理过,而事件仍然会冒出来。
我提出了一个嵌套两个可点击组件的示例。 mousedown事件仅由单击的最内部Clickable组件用户处理:https://codesandbox.io/s/2wmxxzormy
免责声明:我找不到任何关于此的信息,这实际上是一种不好的做法,但通常修改一个你不拥有的对象可能导致例如在将来命名冲突。