Tom*_*Tom 9 javascript race-condition event-propagation reactjs redux
考虑以下典型的React文档结构:
Component.jsx
<OuterClickableArea>
<InnerClickableArea>
content
</InnerClickableArea>
</OuterClickableArea>
Run Code Online (Sandbox Code Playgroud)
这些组件的组成如下:
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>
)
}
}
Run Code Online (Sandbox Code Playgroud)
有了这个参数,InnerClickableArea.js除了类名和控制台日志语句之外几乎有相同的代码.
现在,如果您要运行此应用程序并且用户单击内部可单击区域,则会发生以下情况(如预期的那样):
这显示了典型的事件冒泡/传播 - 到目前为止没有惊喜.
它将输出:
inner area click
outer area click
Run Code Online (Sandbox Code Playgroud)
现在,如果我们创建的应用程序一次只能进行一次交互,该怎么办?例如,想象一下编辑器,可以通过鼠标点击选择元素.当在内部区域内按下时,我们只想选择内部元素.
简单明了的解决方案是在InnerArea组件中添加一个stopPropagation:
InnerClickableArea.js
onMouseDown(e) {
if (!this.state.clicking) {
e.stopPropagation()
this.setState({ clicking: true })
}
}
Run Code Online (Sandbox Code Playgroud)
这按预期工作.它将输出:
inner area click
Run Code Online (Sandbox Code Playgroud)
问题?
InnerClickableArea
特此暗中选择OuterClickableArea
(和所有其他父母)不能接收活动.即使InnerClickableArea
不应该知道存在OuterClickableArea
.就像OuterClickableArea
不知道InnerClickableArea
(关注分离和可重用性概念).我们在两个组件之间创建了一个隐式依赖关系,其中OuterClickableArea
"知道"它不会错误地触发其侦听器,因为它"记住"InnerClickableArea将停止任何事件传播.这似乎是错的.
我正在尝试不使用stopPropagation来使应用程序更具可伸缩性,因为添加依赖于事件的新功能会更容易,而不必记住哪些组件stopPropagation
在哪个时间使用.
相反,我想使上述逻辑更具说明性,例如通过在内部声明地定义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>
Run Code Online (Sandbox Code Playgroud)
哪个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>
)
}
}
Run Code Online (Sandbox Code Playgroud)
问题是Javascript事件循环似乎按以下顺序运行这些操作:
OuterClickableArea
并InnerClickableArea
用丙创建clickable
等于true
(因为应用程序状态interactionBusy默认为false)interactionBusy
设置为true
clickable
支持仍然true
是因为即使应用程序状态interactionBusy
是true
,react还没有导致重新渲染;渲染操作放在Javascript事件循环的末尾)Component.jsx
并将在clickable
为false
这两个组件,但现在这是为时已晚这将输出:
inner area click
outer area click
Run Code Online (Sandbox Code Playgroud)
而期望的输出是:
inner area click
Run Code Online (Sandbox Code Playgroud)
因此,应用程序状态无助于尝试使这些组件更加独立和可重用.原因似乎是即使状态更新,容器也只在事件循环结束时重新呈现,并且鼠标事件传播触发器已在事件循环中排队.
因此,我的问题是:是否有一种替代方法可以使用app状态以声明方式处理鼠标事件传播,还是有更好的方法来实现/执行我的app(redux)状态的上述设置?
最简单的替代方法是在触发 stopPropogation 之前添加一个标志,在这种情况下该标志是一个参数。
const onMouseDown = (stopPropagation) => {
stopPropagation && event.stopPropagation();
}
Run Code Online (Sandbox Code Playgroud)
现在,甚至应用程序状态或道具甚至可以决定触发停止传播
<div onMouseDown={this.onMouseDown(this.state.stopPropagation)} onMouseUp={this.onMouseUp(this.props.stopPropagation)} >
<InnerComponent stopPropagation = {true}>
</div>
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
269 次 |
最近记录: |