反应和模糊事件

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】:

一个快速的方法是:

    onBluronFocus 事件连接到您的父元素 发生模糊时使用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 是返回冒泡的元素(在这种情况下为divonBlur 事件。【参考方案4】:

没有自定义函数和 Internet Explorer 兼容,因为自 Internet Explorer 5 起支持 node.containsdocument.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 总是以&lt;body/&gt; 作为活动元素调用,如this question 和 Felipe 一样,document.activeElement&lt;body/&gt;。相反,我检查relatedTarget 看看它是否是currentTarget 的孩子,就像这样...if (e.relatedTarget &amp;&amp; e.currentTarget.contains(e.relatedTarget)) console.log("child clicked"); Blur 事件先于相关的 Focus 事件,因此当您在 onBlur 处理程序中检查它时,document.activeElement 可能尚未设置为正确的值。除非在任何焦点或模糊事件之前立即设置。【参考方案5】:

问题在于表格实际上没有焦点的概念,因为它本身不是输入。

当 onBlur 在包含的输入上触发时,我们将检查 onBlur 事件的 relatedTarget,该事件应设置为具有 RECEIVED 焦点的元素(或 null)。然后我们使用一个函数从那个新聚焦的元素向上遍历parentNodes,并确保我们的事件的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!马上更新。 我明白你的意思,尽管你总是可以将 divtabindex 设置为 0 :P 我最终将其切换为仅使用 event.currentTarget 来引用表本身,并将其用作我们祖先检查函数中的 parent。这比以前干净得多。如果您不发表评论,我可能不会再看它了。谢谢! 谢谢@alex-thezanke-howard,它有效! :-) 但是一个“改进”:我会使用==!= 而不是=== 来与null 进行比较。 !==;通过这种方式,我们也允许处理undefined - 你永远不知道。 ;-) 但 IE 又出问题了:它不适用于这种方法。 github.com/facebook/react/issues/3751。知道如何让它与 IE 一起使用吗?

以上是关于反应和模糊事件的主要内容,如果未能解决你的问题,请参考以下文章

使用 redux-form 和 react-select.creatable 为啥模糊事件会清除输入?

如何隐藏和显示 SummerNote 工具栏以响应焦点和模糊事件

窗口焦点和模糊事件在 Android 浏览器上无法正常工作

为啥在 iOS Safari Mobile (iPhone / iPad) 中没有触发模糊事件?

在 iframe 主体上设置焦点和模糊事件不适用于 Firefox

如何将模糊事件绑定到实时点击事件?