[英]React: Prop value inside event callback not updated
I have a fairly simple case, I didn't think it would cause me problems.我有一个相当简单的案例,我认为这不会给我带来问题。 If Shift key is pressed I want to add a
e.preventDefault()
to my selectstart
DOM event using React.如果按下 Shift 键,我想使用 React 将
e.preventDefault()
添加到我selectstart
DOM 事件中。 Inoticed that the problem is that the isShiftDown
doesn't update correctly.注意到问题是
isShiftDown
没有正确更新。 I don't understand why this is happening.我不明白为什么会这样。
The isShiftDown
isnside handleSelectStart
function in Paragraph
component is always false
. Paragraph
组件中的isShiftDown
isside handleSelectStart
function 始终为false
。 The same value inside App
component toggle correctly. App
组件内的相同值可以正确切换。
All I have to do is prevent text selecting inside Paragraph if Shift is pressed.如果按下 Shift,我所要做的就是防止在段落内选择文本。 StackBlitz example may seem strange, but my real case much more expanded.
StackBlitz 示例可能看起来很奇怪,但我的真实案例要扩展得多。
// Paragraph const Paragraph = ({ isShiftDown }) => { let paragraphRef; const handleSelectStart = e => { console.log('Paragraph => handleSelectStart => isShiftDown =', isShiftDown); if (isShiftDown) { e.preventDefault(); } }; React.useEffect(() => { paragraphRef.addEventListener('selectstart', handleSelectStart, false); return () => { paragraphRef.removeEventListener('selectstart', handleSelectStart); }; }, []); return ( <p ref={e => (paragraphRef = e)}> Lorem ipsum dolor sit amet, consectetur adipiscing elit. </p> ); }; // App const App = () => { const [isShiftDown, setIsShiftDown] = React.useState(false); const handleKeyUp = e => { if (e.key === 'Shift' && isShiftDown) { setIsShiftDown(false); } }; const handleKeyDown = e => { if (e.key === 'Shift' &&;isShiftDown) { setIsShiftDown(true); } }. React.useEffect(() => { document,addEventListener('keyup', handleKeyUp; false). document,addEventListener('keydown', handleKeyDown; false). return () => { document,removeEventListener('keyup', handleKeyUp; false). document,removeEventListener('keydown', handleKeyDown; false); }, }; []); return ( <div className="App"> <Paragraph isShiftDown={isShiftDown} /> </div> ); }. ReactDOM,render( <App />. document;getElementById("react") );
<div id="react"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
First, your unbinding cannot work, due to the fact that every time the component is re-evaluated, the event handlers' variables point to a new function. You should save a reference to it inside useEffect
or simply use useCallback
when defining the handlers:首先,您的解除绑定无法工作,因为每次重新评估组件时,事件处理程序的变量都会指向一个新的 function。您应该在
useEffect
中保存对它的引用,或者在定义处理程序时简单地使用useCallback
:
const handleKeyUp = useCallback(e => {
if (e.key === "Shift" && setIsShiftDown) {
setIsShiftDown(false);
}
}, [setIsShiftDown])
Also, keyboard events should be binded at the window
level object and not the document
.此外,键盘事件应绑定在
window
级别 object 而不是document
。
You should write it as follows:你应该这样写:
import React, { useEffect, useCallback } from "react";
export default function Paragraph({ isShiftDown }) {
let paragraphRef;
const handleSelectStart = useCallback( <---------- WRAP WITH HOOK
e => {
console.log("Paragraph => handleSelectStart => isShiftDown =", isShiftDown)
if (isShiftDown) {
e.preventDefault()
}
}, [isShiftDown])
useEffect(() => {
const ref = paragraphRef;
ref.addEventListener("selectstart", handleSelectStart);
return () => {
ref.removeEventListener("selectstart", handleSelectStart);
};
}, [isShiftDown]) // <---------- ADD DEPENDENCY
return (
<p ref={el => (paragraphRef = el)}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</p>
)
}
Every time Paragraph
component gets called from App.js
, it basically calls the Paragraph
function, which has its own scope, its own paragraphRef
and so on.每次从
App.js
调用Paragraph
组件时,它基本上都会调用Paragraph
function,它有自己的 scope、自己的paragraphRef
等等。 React can be very confusing if fundamentals are not well understood.如果基础知识没有得到很好的理解,React 可能会非常混乱。
Knowing the above, you will know if a piece of code inside the component is running in the scope of the latest iteration of the component, or a previous one.知道了以上,你就知道组件内部的一段代码是运行在组件最新迭代的scope,还是之前的那个。
How to know?怎么知道? Reading the code carefully and step-by-step simulating the flow in your mind, creating a vision of how it will act on the next time it is called.
仔细阅读代码并逐步模拟您脑海中的流程,创建下一次调用时它将如何执行的愿景。 Knowing JS fundementals and knowing React is key to proper brain-parser which case save a lot of debugging time.
了解JS基础知识和了解React是正确的大脑解析器的关键,这可以节省大量调试时间。 it's all about the brain-parser.
这都是关于大脑解析器的。
Since you did not specify any dependencies for the useEffect in
Paragraph.js`, it will only run once , on the first instance, and that's it.由于您没有
useEffect in
指定任何依赖项,因此它只会在第一个实例上运行一次,仅此而已。
You must re-bind the events every time the component re-renders, so everything will work with the last "version" of the DOM, as you intend, and not previous renders:每次组件重新渲染时,您都必须重新绑定事件,因此一切都将按照您的意图使用 DOM 的最后一个“版本”,而不是以前的渲染:
useEffect(() => {
...
}, [isShiftDown]);
You don't really need to wrap the event callback with useCallback
, but I would prefer to, in this scenario.你真的不需要用
useCallback
包装事件回调,但在这种情况下我更愿意这样做。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.