Javascript 反应状态+;使用效果+;事件提供过时状态

Javascript 反应状态+;使用效果+;事件提供过时状态,javascript,reactjs,codemirror,eventemitter,react-hooks,Javascript,Reactjs,Codemirror,Eventemitter,React Hooks,我试图使用带有Reactuseffect和useState的事件发射器,但它总是获取初始状态,而不是更新状态。如果我直接调用事件处理程序,即使使用setTimeout,它也可以工作 如果我将该值传递给useffect()2参数,它将使其工作,但这会导致每次值更改时(由击键触发)都重新订阅事件发射器 我做错了什么?我尝试了useState、useRef、useReducer、和useCallback,但都无法获得任何工作 这是复制品: import React,{useState,useffec

我试图使用带有React
useffect
useState
的事件发射器,但它总是获取初始状态,而不是更新状态。如果我直接调用事件处理程序,即使使用
setTimeout
,它也可以工作

如果我将该值传递给
useffect()
2参数,它将使其工作,但这会导致每次值更改时(由击键触发)都重新订阅事件发射器

我做错了什么?我尝试了
useState
useRef
useReducer
、和
useCallback
,但都无法获得任何工作

这是复制品:

import React,{useState,useffect}来自“React”;
从“react-codemirror2”导入{控制为CodeMirror};
导入“codemirror/lib/codemirror.css”;
从“事件”导入EventEmitter;
设ee=neweventemitter();
const initialValue=“初始值”;
功能应用程序(道具){
const[value,setValue]=使用状态(initialValue);
//应在初始服务器加载后以及Codemirror输入更改时获取最新值。
const handleEvent=(消息,数据)=>{
info(“事件处理程序中的值:”,值);
//这一行只是为了演示这个问题。如果我们想在这个事件中修改DOM,我们会调用一些setState函数,并以一种反应友好的方式重新渲染。
document.getElementById(“结果”).innerHTML=value;
};
//在组件创建时从服务器获取值(模拟)
useffect(()=>{
设置超时(()=>{
setValue(“来自服务器的值”);
}, 1000);
}, []);
//订阅组件创建时的事件
useffect(()=>{
ee.on(“某些事件”,handleEvent);
return()=>{
ee.关闭(手动通风);
};
}, []);
返回(
{
设置值(新值);
}}
/>
{/*下面的所有内容仅用于演示问题。实际上,事件将来自此组件外部的其他源。*/}
{
ee.发射(“某些事件”);
}}
>
EventEmitter(不工作)
);
}
导出默认应用程序更新了答案

问题不在于挂钩。初始状态值被关闭并传递给EventEmitter,并被反复使用

handleEvent
中直接使用状态值不是一个好主意。相反,我们需要在发出事件时将它们作为参数传递

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);
  const [isReady, setReady] = useState(false);

  // Should get the latest value
  function handleEvent(value, msg, data) {
    // Do not use state values in this handler
    // the params are closed and are executed in the context of EventEmitter
    // pass values as parameters instead
    console.info("Value in event handler: ", value);
    document.getElementById("result").innerHTML = value;
  }

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

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

  function handleClick(e) {
    ee.emit("some_event", value);
  }

  return (
    <React.Fragment>
      <CodeMirror
        value={value}
        options={{ lineNumbers: true }}
        onBeforeChange={(editor, data, newValue) => {
          setValue(newValue);
        }}
      />
      <button onClick={handleClick}>EventEmitter (works now)</button>
      <div id="result" />
    </React.Fragment>
  );
}

export default App;
import React,{useState,useffect}来自“React”;
从“react-codemirror2”导入{控制为CodeMirror};
导入“codemirror/lib/codemirror.css”;
从“事件”导入EventEmitter;
设ee=neweventemitter();
const initialValue=“初始值”;
功能应用程序(道具){
const[value,setValue]=使用状态(initialValue);
const[isReady,setReady]=useState(false);
//应获取最新的值
函数handleEvent(值、消息、数据){
//不要在此处理程序中使用状态值
//参数是关闭的,并在EventEmitter的上下文中执行
//将值作为参数传递
info(“事件处理程序中的值:”,值);
document.getElementById(“结果”).innerHTML=value;
}
//在组件创建时从服务器获取值(模拟)
useffect(()=>{
设置超时(()=>{
setValue(“来自服务器的值”);
setReady(真);
}, 1000);
}, []);
//订阅组件创建时的事件
使用效果(
() => {
如果(isReady){
ee.on(“某些事件”,handleEvent);
}
return()=>{
如果(!ee.off)返回;
ee.关闭(手动通风);
};
},
[伊斯雷迪]
);
函数handleClick(e){
ee.emit(“某个事件”,值);
}
返回(
{
设置值(新值);
}}
/>
EventEmitter(现在工作)
);
}
导出默认应用程序;

下面是一个工作的

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

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

解决方案2:使用
ref
在组件实例变量中存储最新的
值。然后,每次
状态发生变化时,只会更新此变量。在事件处理程序中,使用
ref
,而不是

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);
};


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

useCallback应该在这里工作

import React, { useState, useEffect, useCallback } from "react";
import PubSub from "pubsub-js";
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
  const handler = (msg, data) => {
    console.info("Value in event handler: ", value);
    document.getElementById("result").innerHTML = value;
  };

  const handleEvent = useCallback(handler, [value]);

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

  // Subscribe to events on component creation
  useEffect(() => {
    PubSub.subscribe("some_event", handleEvent);
    return () => {
      PubSub.unsubscribe(handleEvent);
    };
  }, [handleEvent]);
  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);
        }}
      />
      <button
        onClick={() => {
          ee.emit("some_event");
        }}
      >
        EventEmitter (works)
      </button>
      <button
        onClick={() => {
          PubSub.publish("some_event");
        }}
      >
        PubSub (doesnt work)
      </button>
      <button
        onClick={() => {
          setTimeout(() => handleEvent(), 100);
        }}
      >
        setTimeout (works!)
      </button>
      <div id="result" />
    </React.Fragment>
  );
}

export default App;

import React,{useState,useffect,useCallback}来自“React”;
从“PubSub js”导入PubSub;
从“react-codemirror2”导入{控制为CodeMirror};
导入“codemirror/lib/codemirror.css”;
从“事件”导入EventEmitter;
设ee=neweventemitter();
const initialValue=“初始值”;
功能应用程序(道具){
const[value,setValue]=使用状态(initialValue);
//应获取最新的值
常量处理程序=(消息,数据)=>{
info(“事件处理程序中的值:”,值);
document.getElementById(“结果”).innerHTML=value;
};
const handleEvent=useCallback(处理程序,[value]);
//在组件创建时从服务器获取值(模拟)
useffect(()=>{
设置超时(()=>{
setValue(“来自服务器的值”);
}, 1000);
}, []);
//订阅组件创建时的事件
useffect(()=>{
订阅(“某些事件”,handleEvent);
return()=>{
PubSub.unsubscribe(handleEvent);
};
},[handleEvent]);
useffect(()=>{
ee.on(“某些事件”,handleEvent);
return()=>{
ee.关闭(手动通风);
};
}, []);
返回(
{