反应和模糊事件
Posted
技术标签:
【中文标题】反应和模糊事件【英文标题】:React and blur event 【发布时间】:2016-10-27 09:53:42 【问题描述】:我有一个关于 React 和事件处理的简单问题。我的组件看起来像这样(基本上是一个表格):
const MyList = ( items, onBlur ) =>
<table onBlur=onBlur>
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Publisher</th>
<th>Year</th>
<th>Author</th>
<th>System</th>
<th/>
</tr>
</thead>
<tbody>
items.map(item => <MyListRow key=item.Id item=item/>)
</tbody>
</table>;
我希望blur
事件仅在焦点离开表格时触发。相反,当失去焦点时,事件会在表格的每个子元素上触发。
根据文档,React 让焦点事件冒泡。
问题是:我怎样才能让我的onBlur
方法只有在焦点离开表格时才会触发? IOW:如何过滤并丢弃冒泡的不需要的事件,以便仅显示表明表格失去焦点的事件?
【问题讨论】:
【参考方案1】:因此,对于我的用例,我需要监控用户何时离开应用程序,并在此基础上发出警告并更改应用程序的内容,这样用户就无法作弊。话虽这么说,这就是我能够基于@TheZanke Approach 解决它的方法
包装您需要具有此功能的组件并传递道具,并且应该可以完美运行。维护这对我的情况来说更容易,所以我使用它
import PropTypes from "prop-types"
import React, useState, useEffect from 'react'
export default function OutsideAlerter( children, fallback, initialIsVisible, isVisible, onSetIsVisible )
const [isComponentVisible, setIsComponentVisible] = useState(initialIsVisible);
const handleClickOutside = event =>
if (!event?.currentTarget?.contains(event?.relatedTarget))
onSetIsVisible(false)
;
const handleClickInside = event => ;
useEffect(() =>
setIsComponentVisible(isVisible === undefined ? initialIsVisible : isVisible);
, [isVisible])
return (
<div
style=
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
tabIndex="0"
onBlur=handleClickOutside
onClick=handleClickInside
>
isComponentVisible ? children : fallback
</div>
)
OutsideAlerter.propTypes =
children: PropTypes.element,
fallback: PropTypes.element,
initialIsVisible: PropTypes.bool.isRequired,
isVisible: PropTypes.bool,
onSetIsVisible: PropTypes.func
这是通过的后备组件
<OutsideAlerter
fallback=
<div style=
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
color: '#fff',
alignItems: 'center',
height: '100vh',
>
<div>You have clicked outside of the application</div>
<button
name='exam'
onClick=(e) => handleVisible(e, true)
>IsVisible</button>
</div>
initialIsVisible=true
isVisible=isVisible?.exam
onSetIsVisible=(e) => handleVisible( target: name: 'exam' , e)
>
<BaseRoutes />
</OutsideAlerter>
const handleVisible = (e, bool) =>
setIsVisible( ...isVisible, [e.target.name]: bool )
【讨论】:
【参考方案2】:一个快速的方法是:
-
将
onBlur
和onFocus
事件连接到您的父元素
发生模糊时使用setTimeout
启动计时器
焦点发生时清除超时。
看起来像这样:
class MyList extends Component
onBlur()
this.blurTimeoutHandle = setTimeout(this.props.onBlur)
onFocus()
clearTimeout(this.blurTimeoutHandle)
render()
return (
<table onBlur=this.onBlur onFocus=this.onFocus>
...
</table>
)
)
这是可行的,因为所有子模糊都会冒泡到父级,然后焦点事件将取消它们,除非焦点已移到父级之外。
感谢 J. Renée Beach 和她伟大的 Medium article 提供此解决方案的基础知识。
【讨论】:
她文章中链接的codepen 非常有帮助。谢谢。【参考方案3】:希望综合 David Riccitelli 的有用指针和 wintondeshong 的后来见解,这对我有用:
class ActionRow extends Component
onBlur = (e) =>
if ( !e.currentTarget.contains( e.relatedTarget ) )
console.log('blur event');
render()
return (
<div onBlur=this.onBlur>
..
</div>
)
我试图根据焦点何时离开一个充满表单字段的 div 来触发保存操作。
【讨论】:
只是为了澄清那些像我一样无知的人,e.relatedTarget
是即将获得焦点的子元素,e.currentTarget
是返回冒泡的元素(在这种情况下为div
) onBlur
事件。【参考方案4】:
没有自定义函数和 Internet Explorer 兼容,因为自 Internet Explorer 5 起支持 node.contains
和 document.activeElement
,这可以正常工作:
const onBlur = (e) =>
if ( !e.currentTarget.contains( document.activeElement ) )
console.log('table blurred');
Node.contains() 方法返回一个布尔值,指示节点是否是给定节点的后代。 Document.activeElement 返回当前聚焦的元素,即,如果用户键入任何键,该元素将获得击键事件。
【讨论】:
我的修改: if ( event.relatedTarget && event.currentTarget.contains( event.relatedTarget ) ) return console.log('not on blur') 在我的使用中,onBlur
总是以<body/>
作为活动元素调用,如this question
和 Felipe 一样,document.activeElement
是 <body/>
。相反,我检查relatedTarget
看看它是否是currentTarget
的孩子,就像这样...if (e.relatedTarget && e.currentTarget.contains(e.relatedTarget)) console.log("child clicked");
Blur 事件先于相关的 Focus 事件,因此当您在 onBlur 处理程序中检查它时,document.activeElement
可能尚未设置为正确的值。除非在任何焦点或模糊事件之前立即设置。【参考方案5】:
问题在于表格实际上没有焦点的概念,因为它本身不是输入。
当 onBlur 在包含的输入上触发时,我们将检查 onBlur
事件的 relatedTarget
,该事件应设置为具有 RECEIVED 焦点的元素(或 null
)。然后我们使用一个函数从那个新聚焦的元素向上遍历parentNode
s,并确保我们的事件的currentTarget
(表格)不是新聚焦元素的祖先。如果条件通过,则假定该表不再具有任何焦点。
const focusInCurrentTarget = ( relatedTarget, currentTarget ) =>
if (relatedTarget === null) return false;
var node = relatedTarget.parentNode;
while (node !== null)
if (node === currentTarget) return true;
node = node.parentNode;
return false;
const onBlur = (e) =>
if (!focusInCurrentTarget(e))
console.log('table blurred');
const MyList = ( items, onBlur ) => (
<table onBlur=onBlur>
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Publisher</th>
<th>Year</th>
<th>Author</th>
<th>System</th>
<th/>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
<input type="text" />
</td>
<td>
<input type="text" />
</td>
<td>
<input type="text" />
</td>
<td>
<input type="text" />
</td>
<td>
<input type="text" />
</td>
</tr>
</tbody>
</table>
);
ReactDOM.render(
<MyList onBlur=onBlur />,
document.getElementById('root')
);
table
padding: 10px;
border: 1px solid red;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
<br />
<input type="text" />
参考资料:
http://www.w3schools.com/jsref/event_onblur.asp http://www.w3schools.com/jsref/event_currenttarget.asp http://www.w3schools.com/jsref/event_focus_relatedtarget.asp https://***.com/a/2234986/2957138(我们的祖先检查功能的基础)更新:
移除了对 ReactDOM.findDOMNode 的使用
【讨论】:
难道不能简单地将table
包装在div
中并将onBlur
应用于div
吗?如果div
仅包含table
,那么当您从table
中移除焦点时,不会调用它吗?
我认为这与使用表格元素作为 onBlur 的目标没有什么不同,因为它们本身都没有焦点的概念;您仍然需要检查它是否是孩子/父母。虽然在再次查看我的解决方案之后,我确实提出了一个更简洁的想法,可以避免使用 ReactDOM.findDOMNode!马上更新。
我明白你的意思,尽管你总是可以将 div
的 tabindex
设置为 0 :P
我最终将其切换为仅使用 event.currentTarget
来引用表本身,并将其用作我们祖先检查函数中的 parent。这比以前干净得多。如果您不发表评论,我可能不会再看它了。谢谢!
谢谢@alex-thezanke-howard,它有效! :-) 但是一个“改进”:我会使用==
和!=
而不是===
来与null 进行比较。 !==
;通过这种方式,我们也允许处理undefined
- 你永远不知道。 ;-) 但 IE 又出问题了:它不适用于这种方法。 github.com/facebook/react/issues/3751。知道如何让它与 IE 一起使用吗?以上是关于反应和模糊事件的主要内容,如果未能解决你的问题,请参考以下文章
使用 redux-form 和 react-select.creatable 为啥模糊事件会清除输入?
如何隐藏和显示 SummerNote 工具栏以响应焦点和模糊事件
为啥在 iOS Safari Mobile (iPhone / iPad) 中没有触发模糊事件?