programing

React Hooks useState+useEffect+이벤트가 오래된 상태를 나타냄

yellowcard 2023. 4. 5. 21:29
반응형

React Hooks useState+useEffect+이벤트가 오래된 상태를 나타냄

리액트와 함께 이벤트 이미터를 사용하려고 합니다.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;

여기 같은 코드 샌드박스가 있습니다.App2:

https://codesandbox.io/s/ww2v80ww4l

App컴포넌트에는 EventEmitter, pubsub-js 및 setTimeout의 3가지 구현이 있습니다.set Timeout만 동작합니다.

편집

제 목표를 명확히 하기 위해서, 저는 단지 그 가치를handleEvent모든 경우에 Codemirror 값을 일치시킵니다.버튼을 클릭하면 현재 코드미러 값이 표시됩니다.대신 초기값이 표시됩니다.

value는 이벤트 핸들러에서 오래된 것입니다.이는 이벤트 핸들러가 정의된 폐쇄에서 값을 취득하기 때문입니다.매번 새로운 이벤트 핸들러를 재서브스크라이브하지 않는 한value변경 시 새로운 값이 취득되지 않습니다.

해결책 1: 게시 효과에 대한 두 번째 인수를 제시합니다.[value]이것에 의해, 이벤트 핸들러는 올바른 값을 취득할 수 있게 됩니다만, 키 입력 마다 그 효과가 재실행됩니다.

해결책 2: 사용ref최신 정보를 저장하다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-can-i-do-if-my-effect-dependencies-change-too-often

그 페이지에는 다른 해결책도 있는 것 같습니다.도와주신 @Dinesh님 감사합니다.

답변 갱신.

후크에는 문제가 없다.초기 상태 값이 닫히고 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;

여기 작업 코드와 상자가 있습니다.

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;

https://codesandbox.io/s/react-base-forked-i9ro7 에서 코드 앤 박스를 체크해 주세요.

언급URL : https://stackoverflow.com/questions/55154186/react-hooks-usestateuseeffectevent-gives-stale-state

반응형