首页 > 解决方案 > React Hooks useState+useEffect+event 给出过时的状态

问题描述

我正在尝试将事件发射器与 ReactuseEffect和一起使用useState,但它总是获取初始状态而不是更新状态。如果我直接调用事件处理程序,即使使用setTimeout.

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

我究竟做错了什么?我已经尝试过useState, useRef,useReduceruseCallback, 并且无法正常工作。

这是一个复制品:

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;

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

https://codesandbox.io/s/ww2v80ww4l

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

编辑

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

标签: javascriptreactjscodemirroreventemitterreact-hooks

解决方案


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

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

解决方案 2:使用 aref将最新的存储value在组件实例变量中。然后,制作一个效果,它只会在每次value状态更改时更新此变量。在事件处理程序中,使用ref,而不是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 的帮助。


推荐阅读