React Hooks useState+useEffect+event 给出过时的状态 [英] React Hooks useState+useEffect+event gives stale state

查看:26
本文介绍了React Hooks useState+useEffect+event 给出过时的状态的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试将事件发射器与 React useEffectuseState 一起使用,但它总是获得初始状态而不是更新状态.如果我直接调用事件处理程序,即使使用 setTimeout,它也能工作.

I'm trying to use an event emitter with React useEffect and useState, but it always gets the initial state instead of the updated state. It works if I call the event handler directly, even with a setTimeout.

如果我将值传递给 useEffect() 第二个参数,它会使其工作,但是这会导致每次值更改时重新订阅事件发射器(由按键触发).

If I pass the value to the useEffect() 2nd argument, it makes it work, however this causes a resubscription to the event emitter every time the value changes (which is triggered off of keystrokes).

我做错了什么?我尝试了 useStateuseRefuseReduceruseCallback,但都无法正常工作.

What am I doing wrong? I've tried useState, useRef, useReducer, and useCallback, and couldn't get any working.

这是一个复制品:

import React, { useState, useEffect } from "react";
import { Controlled as CodeMirror } from "react-codemirror2";
import "codemirror/lib/codemirror.css";
import EventEmitter from "events";

let ee = new EventEmitter();

const initialValue = "initial value";

function App(props) {
  const [value, setValue] = useState(initialValue);

  // Should get the latest value, both after the initial server load, and whenever the Codemirror input changes.
  const handleEvent = (msg, data) => {
    console.info("Value in event handler: ", value);
    // This line is only for demoing the problem. If we wanted to modify the DOM in this event, we would instead call some setState function and rerender in a React-friendly fashion.
    document.getElementById("result").innerHTML = value;
  };

  // Get value from server on component creation (mocked)
  useEffect(() => {
    setTimeout(() => {
      setValue("value from server");
    }, 1000);
  }, []);

  // Subscribe to events on component creation
  useEffect(() => {
    ee.on("some_event", handleEvent);
    return () => {
      ee.off(handleEvent);
    };
  }, []);

  return (
    <React.Fragment>
      <CodeMirror
        value={value}
        options={{ lineNumbers: true }}
        onBeforeChange={(editor, data, newValue) => {
          setValue(newValue);
        }}
      />
      {/* Everything below is only for demoing the problem. In reality the event would come from some other source external to this component. */}
      <button
        onClick={() => {
          ee.emit("some_event");
        }}
      >
        EventEmitter (doesnt work)
      </button>
      <div id="result" />
    </React.Fragment>
  );
}

export default App;

这是一个与 App2 中相同的代码沙箱:

Here's a code sandbox with the same in App2:

https://codesandbox.io/s/ww2v80ww4l

App 组件有 3 种不同的实现 - EventEmitter、pubsub-js 和 setTimeout.只有 setTimeout 有效.

App component has 3 different implementations - EventEmitter, pubsub-js, and setTimeout. Only setTimeout works.

为了阐明我的目标,我只是希望 handleEvent 中的值在所有情况下都与 Codemirror 值匹配.单击任何按钮时,应显示当前的 codemirror 值.而是显示初始值.

To clarify my goal, I simply want the value in handleEvent to match the Codemirror value in all cases. When any button is clicked, the current codemirror value should be displayed. Instead, the initial value is displayed.

推荐答案

value 在事件处理程序中是陈旧的,因为它从定义它的闭包中获取它的值.除非我们在每次 value 改变时重新订阅一个新的事件处理程序,否则它不会得到新的值.

value is stale in the event handler because it gets its value from the closure where it was defined. Unless we re-subscribe a new event handler every time value changes, it will not get the new value.

解决方案 1:为发布效果 [value] 设置第二个参数.这使得事件处理程序获得正确的值,但也会导致每次击键时再次运行效果.

Solution 1: Make the second argument to the publish effect [value]. This makes the event handler get the correct value, but also causes the effect to run again on every keystroke.

解决方案 2:使用 ref 将最新的 value 存储在组件实例变量中.然后,创建一个效果,每次 value 状态改变时都会更新这个变量.在事件处理程序中,使用 ref,而不是 value.

Solution 2: Use a ref to store the latest value in a component instance variable. Then, make an effect which does nothing but update this variable every time value state changes. In the event handler, use the ref, not value.

const [value, setValue] = useState(initialValue);
const refValue = useRef(value);
useEffect(() => {
    refValue.current = value;
});
const handleEvent = (msg, data) => {
    console.info("Value in event handler: ", refValue.current);
};

https://reactjs.org/docs/hooks-faq.html#what-c​​an-i-do-if-my-effect-dependencies-change-too-often

看起来该页面上还有一些其他解决方案也可能有效.非常感谢@Dinesh 的帮助.

Looks like there are some other solutions on that page which might work too. Much thanks to @Dinesh for the assistance.

这篇关于React Hooks useState+useEffect+event 给出过时的状态的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆