当前位置: 首页 > 面试题库 >

错误的React将事件监听器的行为挂钩

南门向荣
2023-03-14
问题内容

我在玩React钩子,遇到了一个问题。当我尝试使用由事件侦听器处理的按钮来控制台记录日志时,它显示错误的状态。

CodeSandbox:

  1. 点击“添加卡”按钮两次
  2. 在第一张卡中,单击Button1并在控制台中查看有2张卡处于状态(正确行为)
  3. 在第一张卡中,单击Button2(由事件侦听器处理),然后在控制台中看到只有1张卡处于状态(行为错误)

为什么显示错误状态?在第一张卡片中,Button2应该在控制台中显示2张卡片。有任何想法吗?

import React, { useState, useContext, useRef, useEffect } from "react";

import ReactDOM from "react-dom";

import "./styles.css";



const CardsContext = React.createContext();



const CardsProvider = props => {

  const [cards, setCards] = useState([]);



  const addCard = () => {

    const id = cards.length;

    setCards([...cards, { id: id, json: {} }]);

  };



  const handleCardClick = id => console.log(cards);

  const handleButtonClick = id => console.log(cards);



  return (

    <CardsContext.Provider

      value={{ cards, addCard, handleCardClick, handleButtonClick }}

    >

      {props.children}

    </CardsContext.Provider>

  );

};



function App() {

  const { cards, addCard, handleCardClick, handleButtonClick } = useContext(

    CardsContext

  );



  return (

    <div className="App">

      <button onClick={addCard}>Add card</button>

      {cards.map((card, index) => (

        <Card

          key={card.id}

          id={card.id}

          handleCardClick={() => handleCardClick(card.id)}

          handleButtonClick={() => handleButtonClick(card.id)}

        />

      ))}

    </div>

  );

}



function Card(props) {

  const ref = useRef();



  useEffect(() => {

    ref.current.addEventListener("click", props.handleCardClick);

    return () => {

      ref.current.removeEventListener("click", props.handleCardClick);

    };

  }, []);



  return (

    <div className="card">

      Card {props.id}

      <div>

        <button onClick={props.handleButtonClick}>Button1</button>

        <button ref={node => (ref.current = node)}>Button2</button>

      </div>

    </div>

  );

}



ReactDOM.render(

  <CardsProvider>

    <App />

  </CardsProvider>,

  document.getElementById("root")

);

我使用React 16.7.0-alpha.0和Chrome 70.0.3538.110


问题答案:

对于使用useState钩子的功能组件,这是常见的问题。相同的考虑适用于useState使用状态的任何回调函数,例如setTimeoutsetInterval定时器函数。

事件处理程序在CardsProviderCard组件中被区别对待。

handleCardClick并且handleButtonClickCardsProvider功能组件中使用的组件在其范围内定义。每次运行时都有新功能,它们引用cards在定义它们时获得的状态。每次CardsProvider呈现组件时都会重新注册事件处理程序。

handleCardClick用于Card功能组件的组件会作为道具接收并在组件支架上一次注册useEffect。它在整个组件寿命期间都具有相同的功能,并且是指在首次handleCardClick定义功能时新鲜的陈旧状态。handleButtonClick作为道具接收并在每个Card渲染器上重新注册,每次都是新功能,并引用新鲜状态。

可变状态

解决此问题的常用方法是使用useRef而不是useState。引用基本上是一种配方,提供了一个可变对象,可以通过引用传递该对象:

const ref = useRef(0);

function eventListener() {
  ref.current++;
}

万一组件应该在状态更新时重新渲染,如预期的那样useState,则refs不适用。

可以分别保持状态更新和可变状态,但是forceUpdate在类和函数组件中都被视为反模式(列出仅供参考):

const useForceUpdate = () => {
  const [, setState] = useState();
  return () => setState({});
}

const ref = useRef(0);
const forceUpdate = useForceUpdate();

function eventListener() {
  ref.current++;
  forceUpdate();
}

状态更新器功能

一种解决方案是使用状态更新程序功能,该功能从封闭的范围接收新鲜状态而不是陈旧状态:

function eventListener() {
  // doesn't matter how often the listener is registered
  setState(freshState => freshState + 1);
}

如果需要一个状态来实现同步副作用,例如console.log,一种解决方法是返回相同状态以防止更新。

function eventListener() {
  setState(freshState => {
    console.log(freshState);
    return freshState;
  });
}

useEffect(() => {
  // register eventListener once
}, []);

这不适用于异步副作用,尤其是async函数。

手动事件侦听器重新注册

另一种解决方案是每次都重新注册事件侦听器,因此回调总是从封闭范围获得新状态:

function eventListener() {
  console.log(state);
}

useEffect(() => {
  // register eventListener on each state update
}, [state]);

内置事件处理

除非在上注册了事件侦听器documentwindow或者其他事件目标不在当前组件的范围之内,否则在可能的情况下必须使用React自己的DOM事件处理,这样就不需要useEffect

<button onClick={eventListener} />

在最后一种情况下,事件侦听器可以作为道具传递时,还可以通过useMemouseCallback来记住,以防止不必要的重新渲染:

const eventListener = useCallback(() => {
  console.log(state);
}, [state]);

答案的先前版本建议使用可变状态,该可变状态适用useState于React16.7.0-alpha版本中的初始钩子实现,但不适用于最终的React16.8实现。useState当前仅支持不可变状态。



 类似资料:
  • 问题内容: 我在玩React钩子,遇到了一个问题。当我尝试使用事件侦听器处理的按钮来控制台记录日志时,它显示错误的状态。 CodeSandbox: https ://codesandbox.io/s/lrxw1wr97m 点击“添加卡”按钮两次 在第一张卡中,单击“ Button1”,然后在控制台中看到有2张卡处于状态(正确行为) 在第一张卡中,单击Button2(由事件侦听器处理),然后在控制台

  • 应用事件监听器是实现一个或多个 Servlet 事件监听器接口的类。它们是在部署 Web 应用时,实例化并注册到 Web 容器中。它们由开发人员在WAR 包中提供。 Servlet 事件监听器支持在 ServletContext、HttpSession 和ServletRequest 状态改变时进行事件通知。Servlet 上下文监听器是用来管理应用的资源或 JVM 级别持有的状态。HTTP 会话

  • 我正在使用Hibernate 5和Spring 4.2。3.我找不到将eventListener添加到SessionFactory范围的方法。我只需要在hibernate持久化对象之前设置一个日期。我在Spring定义了sessionFactory。xml 我有我的GenericDAOImpl在哪里得到这个会话工厂: 我已经看到了几种方法,但其中一些方法不适用于Hibernate 5(如)。我找不

  • 主要内容:什么是AWT事件监听器,AWT EventListner 接口声明,常用的AWT事件监听器什么是AWT事件监听器 AWT事件侦听器代表负责处理事件的接口。Java 为我们提供了各种事件侦听器类,但我们将讨论更常用的那些。事件侦听器方法的每个方法都有一个作为对象的参数,该对象是 EventObject 类的子类。例如,鼠标事件侦听器方法将接受 MouseEvent 的实例,其中 MouseEvent 派生自 EventObject。 AWT EventListner 接口声明 Event

  • Blade 中提供一个方法帮助开发者可以自定义的监听应用程序运行中的一些生命周期。比如 Session 的创建与销毁,应用启动结束后等。 支持的事件类型有如下: public enum EventType { SERVER_STARTING, // 服务准备启动 SERVER_STARTED, // 服务启动成功 SERVER_STOPPING, //

  • Nutz.Ioc 容器有三种事件: 对象被创建(create事件) 对象被从容器中取出(fetch事件) 对象被销毁(depose事件) 在这三种时刻,你如果想做一些特殊的操作,比如,当一个数据源被销毁时,你希望能够关闭所有的连接, 声明一下,你想监听什么事件,以及怎么监听。 注: 如果你的对象是 "singleton: false",那么容器创建了对象后就会立即忘记它的存在。因为鬼才知道 你打算