反应和模糊事件
React and blur event
我对 React 和事件处理有一个简单的问题。我的组件看起来像这样(基本上是 table):
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
事件仅在焦点离开 table 时触发 。相反,事件在 table 的每个子元素失去焦点时触发。
根据文档,React 让焦点事件冒泡。
问题是:如何让我的 onBlur
方法仅在焦点离开 table 时触发? IOW:如何过滤掉冒泡的不需要的事件,以便我只显示表明 table 失去焦点的事件?
问题是 table 实际上没有焦点的概念,因为它本身不是输入。
当 onBlur 在包含的输入上触发时,我们将检查 onBlur
事件的 relatedTarget
,该事件应设置为已获得焦点(或 null
)的元素。然后我们使用一个函数,该函数将从那个新聚焦的元素向上遍历 parentNode
s 并确保我们事件的 currentTarget
(table)不是新聚焦元素的祖先。如果条件通过,则假定 table 不再有任何焦点。
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
- (我们祖先检查功能的基础)
更新:
删除了对 ReactDOM.findDOMNode
的使用
没有 自定义函数和 Internet Explorer 兼容,因为自 Internet Explorer 5 起支持 node.contains
and document.activeElement
,这有效:
const onBlur = (e) => {
if ( !e.currentTarget.contains( document.activeElement ) ) {
console.log('table blurred');
}
}
- The Node.contains() method returns a Boolean value indicating whether a node is a descendant of a given node or not.
- Document.activeElement returns the currently focused element, that is, the element that will get keystroke events if the user types any.
希望综合 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 充满表单字段的时间来触发保存操作。
一个快速的方法是:
- 将
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 此解决方案的基础知识。
所以对于我的用例,我需要监视用户何时离开应用程序并基于此发出警告并更改应用程序的内容,这样用户就无法作弊。话虽这么说,这就是我能够基于 方法
解决它的方法
包装您需要的组件以实现此功能并传递道具,应该可以完美运行。维护这个对我来说更容易,所以我使用这个
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 })
}
我对 React 和事件处理有一个简单的问题。我的组件看起来像这样(基本上是 table):
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
事件仅在焦点离开 table 时触发 。相反,事件在 table 的每个子元素失去焦点时触发。
根据文档,React 让焦点事件冒泡。
问题是:如何让我的 onBlur
方法仅在焦点离开 table 时触发? IOW:如何过滤掉冒泡的不需要的事件,以便我只显示表明 table 失去焦点的事件?
问题是 table 实际上没有焦点的概念,因为它本身不是输入。
当 onBlur 在包含的输入上触发时,我们将检查 onBlur
事件的 relatedTarget
,该事件应设置为已获得焦点(或 null
)的元素。然后我们使用一个函数,该函数将从那个新聚焦的元素向上遍历 parentNode
s 并确保我们事件的 currentTarget
(table)不是新聚焦元素的祖先。如果条件通过,则假定 table 不再有任何焦点。
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
- (我们祖先检查功能的基础)
更新:
删除了对 ReactDOM.findDOMNode
的使用没有 自定义函数和 Internet Explorer 兼容,因为自 Internet Explorer 5 起支持 node.contains
and document.activeElement
,这有效:
const onBlur = (e) => {
if ( !e.currentTarget.contains( document.activeElement ) ) {
console.log('table blurred');
}
}
- The Node.contains() method returns a Boolean value indicating whether a node is a descendant of a given node or not.
- Document.activeElement returns the currently focused element, that is, the element that will get keystroke events if the user types any.
希望综合 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 充满表单字段的时间来触发保存操作。
一个快速的方法是:
- 将
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 此解决方案的基础知识。
所以对于我的用例,我需要监视用户何时离开应用程序并基于此发出警告并更改应用程序的内容,这样用户就无法作弊。话虽这么说,这就是我能够基于
包装您需要的组件以实现此功能并传递道具,应该可以完美运行。维护这个对我来说更容易,所以我使用这个
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 })
}