首页 > 解决方案 > React 生命周期与窗口事件竞争条件

问题描述

我希望有人可以阐明 React 的状态和事件处理程序如何与直接在窗口对象本身上设置的事件处理程序交互(在反应生命周期之外)。例如,在以下代码示例中,我们直接在window对象上设置事件处理程序。现在我对浏览器如何处理窗口事件的理解是,当事件触发时,浏览器会将回调函数放入事件循环的队列中。

在这种情况下,处理程序直接设置在窗口上,事件处理程序函数进入事件循环的队列发生在/独立于 React 组件生命周期之外。但是,事件处理程序调用 ReactsetState异步更新组件状态。更新是异步发生的,因此 React 可以更有效地将状态更改批处理在一起,以避免过于频繁地重新渲染。

import { Component, createRef } from "react";

class GridView extends Component {

  constructor(props) {
    super(props);
    this.state = {
      hasScrolled: false
    }
    
    this.handleScrollEvent = this.handleScrollEvent.bind(this);
  }

  componentDidMount() {
    window.addEventListener("scroll", this.handleScrollEvent);
  }

  componentWillUnmount() {
    window.removeEventListener("scroll", this.handleScrollEvent);
  }

  handleScrollEvent() {
    this.setState({hasScrolled: true})
  }

  render() {
    return <div > ... some scrollable content </div>
  }
  
}

我的问题是:我可以确定当handleScrollEvent第一次触发回调时,页面初始滚动后,组件的hasScrolled状态值将更新到true下一次触发之前handleScrollEvent

我的理解是,React 的异步特性setState使得我无法 100% 确定何时会更新与绑定到window. 我是否正确理解这一点?

更进一步,假设我要通过onScroll在组件的方法中添加回调来将滚动事件处理程序移动到 React 组件生命周期中,render如下所示。我的假设是,即使滚动事件处理程序和状态更新都在 React 中进行管理,但我仍然无法确定回调和状态更改的确切顺序,因为 React 中的事件处理程序是异步和批处理的,就像setState.

import { Component, createRef } from "react";

class GridView extends Component {

  constructor(props) {
    super(props);
    this.state = {
      hasScrolled: false
    }
    
    this.handleScrollEvent = this.handleScrollEvent.bind(this);
  }

  handleScrollEvent() {
    this.setState({hasScrolled: true})
  }

  render() {
    return <div onScroll={ this.handleScrollEvent } > ... some scrollable content </div>
  }
  
}

那么,你真的可以确定与 React 中的事件处理程序相关的状态更新的顺序吗?如果答案是否定的,您永远无法 100% 确定,那么在实例变量同步更新的情况下,事件处理程序内部的逻辑是否应该依赖组件的实例变量而不是组件的状态?

标签: reactjsreact-statereact-lifecycle

解决方案


推荐阅读