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
'programing' 카테고리의 다른 글
파이어베이스의 컬렉션에 여러 문서를 추가하려면 어떻게 해야 합니까? (0) | 2023.04.05 |
---|---|
Nextjs: 정적 폴더에서 이미지를 로드할 수 없습니다. (0) | 2023.04.05 |
반응의 PropTypes.node와 PropTypes.any의 차이점은 무엇입니까? (0) | 2023.04.05 |
JavaScript에서 fetch() API 요청 및 응답 가로채기 (0) | 2023.04.05 |
React의 인라인 CSS 스타일: 미디어 쿼리를 구현하는 방법 (0) | 2023.04.05 |