React Hooks useState + useEffect + event给出陈旧状态

Ton*_*y R 10 javascript codemirror eventemitter reactjs react-hooks

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

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

我究竟做错了什么?我试过useState,useRef,useReducer,和useCallback,并不能得到任何工作。

这是复制品:

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;
Run Code Online (Sandbox Code Playgroud)

这是一个具有以下代码的代码沙箱App2:

https://codesandbox.io/s/ww2v80ww4l

App组件具有3种不同的实现-EventEmitter,pubsub-js和setTimeout。仅setTimeout有效。

谢谢!

编辑:为了阐明我的目标,我只是希望handleEvent所有情况下的值都与Codemirror值匹配。单击任何按钮时,应显示当前的代码镜像值。而是显示初始值。

Ton*_*y R 13

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

解决方案1:对发布效果进行第二个论证[value]。这使事件处理程序获得正确的值,但也使效果在每次击键时再次运行。

解决方案2:使用ref将最新的存储value在组件实例变量中。然后,产生一个效果,每当value状态更改时,该效果仅更新此变量。在事件处理程序中ref,不要使用value。

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

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

该页面上似乎还有其他一些解决方案也可能起作用。非常感谢@Dinesh的帮助。

  • 刚刚注意到此用例已在此处的React Hooks RFC的缺点部分中明确描述:https://github.com/reactjs/rfcs/blob/master/text/0068-react-hooks.md#drawbacks (2认同)
  • 实际上,这里的 useState 应该有第二个参数 [] ,否则每次渲染时都会调用它,而不需要。 (2认同)