英文:
useState value used setInterval is not updating in addEventListener
问题
I'm trying to pass the value to addEventListener(mousemove)
from setInterval
. Somehow it's not working properly. Can someone help?
const [date, setDate] = useState(new Date());
function refreshClock() {
setDate(new Date());
}
var x = 0;
var y = 0;
const [text, setText] = useState('');
let windowHeight = window.innerHeight/2;
let windowWidth = window.innerWidth/2;
useEffect(() => {
const timerId = setInterval(refreshClock, 1000);
const onMouseMove = function(e) {
x = e.clientX;
y = e.clientY;
if (x < windowWidth + 100 && x > windowWidth - 100 && y < windowHeight + 100 && y > windowHeight - 100) {
setText(`${date.toLocaleTimeString('en-GB')}`);
} else {
setText('');
}
}
window.addEventListener("mousemove", onMouseMove)
return function cleanup() {
clearInterval(timerId);
window.removeEventListener("mousemove", onMouseMove)
};
}, []);
return (
<>
{text}
</>
)
I'm trying to make time show when the mouse is around the center of the window. setInterval
is working fine without addEventListener
.
英文:
I'm trying to pass the value to addEventListener(mousemove)
from setInterval
. Somehow it's not working properly. Can someone help?
const [date, setDate] = useState(new Date());
function refreshClock() {
setDate(new Date());
}
var x = 0;
var y = 0;
const [text, setText] = useState('');
let windowHeight = window.innerHeight/2;
let windowWidth = window.innerWidth/2;
useEffect(() => {
const timerId = setInterval(refreshClock, 1000);
const onMouseMove = function(e) {
x = e.clientX;
y = e.clientY;
if (x < windowWidth+100 && x > windowWidth-100 && y < windowHeight+100 && y > windowHeight-100 ) {
setText(`${date.toLocaleTimeString('en-GB')}`);
} else {
setText('');
}
}
window.addEventListener("mousemove", onMouseMove)
return function cleanup() {
clearInterval(timerId);
window.removeEventListener("mousemove", onMouseMove)
};
}, []);
return (
<>
{text}
</>
)
I'm trying to make time shows when the mouse is around the center of the window. setInterval is working fine without addEventListener.
答案1
得分: 2
以下是您要翻译的内容:
"您目前遇到的主要问题是 -
- 您的
useEffect
仅运行一次 onMouseMove
在效果内声明onMouseMove
封闭在date
上date
将始终相同,因为onMouseMove
从未获得更新的值- 因此,
setText
仅显示第一个date
您遇到的另一个问题是 -
- 窗口宽度/高度在效果外计算一次
onMouseMove
封闭在这些值上- 如果窗口大小调整,事件侦听器将保留旧值
我们可以通过将 onMouseMove
移出效果来解决这些问题。而不是将 date
状态复制到 text
,我们将使用 useRef
来简单地切换可见性开/关。在React中,这被称为保持 单一真相来源。以下是一个最小的可重现示例 -
<!-- begin snippet: js hide: false console: true babel: true -->
<!-- language: lang-js -->
function App() {
const [date, setDate] = React.useState(new Date())
const isDateVisible = React.useRef(false)
const refreshClock = () =>
setDate(new Date()) // 更新时钟
const onMouseMove = event => {
const wx = window.innerWidth/2
const wy = window.innerHeight/2
const x = event.clientX
const y = event.clientY
isDateVisible.current = // 更新可见性
x < wx+100 && x > wx-100 && y < wy+100 && y > wy-100
}
React.useEffect(
() => {
const t = setInterval(refreshClock, 1000)
addEventListener("mousemove", onMouseMove)
return () => {
clearInterval(t)
removeEventListener("mousemove", onMouseMove)
}
},
[] // refreshClock 和 onMouseMove 没有依赖项
)
return isDateVisible.current
&& date.toLocaleTimeString('en-GB')
}
ReactDOM.createRoot(document.querySelector("#app")).render(<App />)
<!-- language: lang-css -->
body::before { content: "将光标移动到中心"; }
<!-- language: lang-html -->
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>
<!-- end snippet -->
严格的依赖关系
一般来说,您需要将任何自由变量作为效果的依赖项包括进去。如果我们要严格一点,refreshClock
和 onMouseMove
将被列为依赖项,但它们自己没有依赖项,因此可以安全地排除它们。
refreshClock
和 onMouseMove
每次 date
状态更改并重新渲染组件时都会被重新创建。如果将它们列为 useEffect
的依赖项,那么效果也会重新运行,这是我们希望避免的。
使用 useCallback
可确保 refreshClock
和 onMouseMove
将指向相同的函数对象,以防止不必要地重新运行效果。这个更新后的程序与上面的程序具有相同的行为,但更加严格,可能更清晰地传达了意图。
<!-- begin snippet: js hide: false console: true babel: true -->
<!-- language: lang-js -->
function App() {
const [date, setDate] = React.useState(new Date())
const isDateVisible = React.useRef(false)
const refreshClock = React.useCallback(
() => setDate(new Date()),
[] // 零依赖
)
const onMouseMove = React.useCallback(
event => {
const wx = window.innerWidth/2
const wy = window.innerHeight/2
const x = event.clientX
const y = event.clientY
isDateVisible.current =
x < wx+100 && x > wx-100 && y < wy+100 && y > wy-100
},
[] // 零依赖
)
React.useEffect(
() => {
const t = setInterval(refreshClock, 1000)
addEventListener("mousemove", onMouseMove)
return () => {
clearInterval(t)
removeEventListener("mousemove", onMouseMove)
}
},
[refreshClock, onMouseMove] // 严格依赖
)
return isDateVisible.current
&& date.toLocaleTimeString('en-GB')
}
ReactDOM.createRoot(document.querySelector("#app")).render(<App />)
<!-- language: lang-css -->
body::before { content: "将光标移动到中心"; }
<!-- language: lang-html -->
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>
<!-- end snippet -->
最后说明:
- 不要将状态设置函数包括为依赖项。
setDate
已经保证是来自useState
的不变值 - 不要将引用包括为依赖项。引用不代表响应式状态更改,因此不会触发渲染。"
英文:
The main issue you are having is that -
- your
useEffect
runs only once onMouseMove
is declared inside the effectonMouseMove
closes overdate
date
will always be the same because theonMouseMove
never gets an updated valuesetText
therefore will only display the firstdate
Another issue you are having is -
- window width/height are computed once outside the effect
onMouseMove
closes over these values- if the window is resized, the event listener will keep the old values
We can fix these issues by moving onMouseMove
outside the effect. And instead of copying date
state to text
, we will use useRef
to simply toggle the visibility on/off. In react this is known as keeping a single source of truth. Here's a minimal reproducible example -
<!-- begin snippet: js hide: false console: true babel: true -->
<!-- language: lang-js -->
function App() {
const [date, setDate] = React.useState(new Date())
const isDateVisible = React.useRef(false)
const refreshClock = () =>
setDate(new Date()) // update clock
const onMouseMove = event => {
const wx = window.innerWidth/2
const wy = window.innerHeight/2
const x = event.clientX
const y = event.clientY
isDateVisible.current = // update visibility
x < wx+100 && x > wx-100 && y < wy+100 && y > wy-100
}
React.useEffect(
() => {
const t = setInterval(refreshClock, 1000)
addEventListener("mousemove", onMouseMove)
return () => {
clearInterval(t)
removeEventListener("mousemove", onMouseMove)
}
},
[] // refreshClock and onMouseMove have zero deps
)
return isDateVisible.current
&& date.toLocaleTimeString('en-GB')
}
ReactDOM.createRoot(document.querySelector("#app")).render(<App />)
<!-- language: lang-css -->
body::before { content: "move the cursor to the center"; }
<!-- language: lang-html -->
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>
<!-- end snippet -->
strict dependencies
In general, you will need to include any free variables as dependencies to your effects. If we were to be strict, refreshClock
and onMouseMove
would be listed as dependencies, but they have zero dependencies of their own and so it's safe to leave them out.
refreshClock
and onMouseMove
are recreated each time date
state changes and the component is re-rendered. If we list them as dependencies to useEffect
then the effect will re-run too, which we'd like to avoid.
Using useCallback
ensures that refreshClock
and onMouseMove
will point to the same function object for each render, and prevents the effect from re-running unnecessarily. This updated program has the same behaviour as the one above, but it is more strict, and possibly communicates intentions more clearly.
<!-- begin snippet: js hide: false console: true babel: true -->
<!-- language: lang-js -->
function App() {
const [date, setDate] = React.useState(new Date())
const isDateVisible = React.useRef(false)
const refreshClock = React.useCallback(
() => setDate(new Date()),
[] // zero dependencies
)
const onMouseMove = React.useCallback(
event => {
const wx = window.innerWidth/2
const wy = window.innerHeight/2
const x = event.clientX
const y = event.clientY
isDateVisible.current =
x < wx+100 && x > wx-100 && y < wy+100 && y > wy-100
},
[] // zero dependencies
)
React.useEffect(
() => {
const t = setInterval(refreshClock, 1000)
addEventListener("mousemove", onMouseMove)
return () => {
clearInterval(t)
removeEventListener("mousemove", onMouseMove)
}
},
[refreshClock, onMouseMove] // strict dependencies
)
return isDateVisible.current
&& date.toLocaleTimeString('en-GB')
}
ReactDOM.createRoot(document.querySelector("#app")).render(<App />)
<!-- language: lang-css -->
body::before { content: "move the cursor to the center"; }
<!-- language: lang-html -->
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>
<!-- end snippet -->
Final notes:
- Do not include state setter function as dependency.
setDate
is already guaranteed to be a non-changing value fromuseState
- Do not include references as dependencies. Refs do not represent reactive state change and therefore do not trigger renders.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论