【问题标题】: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:如何过滤并丢弃冒泡的不需要的事件,以便仅显示表明表格失去焦点的事件?

【问题讨论】:

    标签: javascript html reactjs dom-events


    【解决方案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】:

      一个快速的方法是:

      1. onBluronFocus 事件连接到您的父元素
      2. 发生模糊时使用setTimeout 启动计时器
      3. 焦点发生时清除超时。

      看起来像这样:

      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" />

      参考资料:

      更新:

      移除了对 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 一起使用吗?
      猜你喜欢
      • 2011-11-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多