Javascript 事件侦听器的错误行为
我在胡闹,遇到了一个问题。 当我试图使用事件侦听器处理的按钮来控制台记录日志时,它显示了错误的状态 代码沙盒:Javascript 事件侦听器的错误行为,javascript,reactjs,react-hooks,Javascript,Reactjs,React Hooks,我在胡闹,遇到了一个问题。 当我试图使用事件侦听器处理的按钮来控制台记录日志时,它显示了错误的状态 代码沙盒: 点击“添加卡”按钮2次 在第一张卡中,单击按钮1,在控制台中看到有两张卡处于状态(正确的行为) 在第一张卡中,单击按钮2(由事件侦听器处理),并在控制台中看到只有一张卡处于状态(错误行为) 为什么它显示了错误的状态 在第一张卡中,按钮2应在控制台中显示2卡。有什么想法吗 const{useState,useContext,useRef,useffect}=React; const C
在第一张卡中,
按钮2
应在控制台中显示2
卡。有什么想法吗
const{useState,useContext,useRef,useffect}=React;
const CardsContext=React.createContext();
const CardsProvider=道具=>{
const[cards,setCards]=useState([]);
const addCard=()=>{
const id=cards.length;
setCards([…cards,{id:id,json:{}]);
};
const handleCardClick=id=>console.log(卡片);
const handleButtonClick=id=>console.log(卡片);
返回(
{props.children}
);
};
函数App(){
const{cards,addCard,handleCardClick,handleButtonClick}=useContext(
CardsContext
);
返回(
添加卡
{cards.map((卡片,索引)=>(
handleCardClick(card.id)}
handleButtonClick={()=>handleButtonClick(card.id)}
/>
))}
);
}
功能卡(道具){
const ref=useRef();
useffect(()=>{
ref.current.ADDEVENTLISTER(“点击”,道具手柄点击);
return()=>{
ref.current.removeEventListener(“单击”,props.handleCardClick);
};
}, []);
返回(
卡片{props.id}
按钮1
(ref.current=node)}>按钮2
);
}
ReactDOM.render(
,
document.getElementById(“根”)
);代码>
这是使用useState
hook的功能组件的常见问题。同样的问题也适用于使用useState
state的任何回调函数,例如
事件处理程序在CardsProvider
和Card
组件中的处理方式不同
handleCardClick
和handleButtonClick
用于CardsProvider
的功能组件在其范围内定义。每次运行时都会有新函数,它们指的是在定义卡片时获得的状态。每次呈现CardsProvider
组件时,都会重新注册事件处理程序
handleCardClick
卡中使用的功能组件作为道具接收,并在组件安装时使用useffect
注册一次。在整个组件寿命期间,它是相同的函数,指的是在第一次定义handleCardClick
函数时新出现的陈旧状态handleButtonClick
作为道具接收,并在每个卡
渲染上重新注册,它每次都是一个新函数,表示新状态
可变态
解决此问题的常用方法是使用useRef
而不是useState
。ref基本上是一个配方,它提供了一个可通过引用传递的可变对象:
const ref = useRef(0);
function eventListener() {
ref.current++;
}
如果组件应该在状态更新时重新呈现,就像在useState
中预期的那样,则参考不适用
可以将状态更新和可变状态分开保存,但类和函数组件中的forceUpdate
都被视为反模式(列出仅供参考):
状态更新程序函数
一种解决方案是使用状态更新程序函数,该函数从封闭范围接收新鲜状态,而不是陈旧状态:
function eventListener() {
// doesn't matter how often the listener is registered
setState(freshState => freshState + 1);
}
function eventListener() {
console.log(state);
}
useEffect(() => {
// register eventListener on each state update
return () => {
// unregister eventListener
};
}, [state]);
如果同步副作用(如console.log
)需要一个状态,解决方法是返回相同的状态以防止更新
function eventListener() {
setState(freshState => {
console.log(freshState);
return freshState;
});
}
useEffect(() => {
// register eventListener once
return () => {
// unregister eventListener once
};
}, []);
对于异步副作用,尤其是async
函数,这种方法效果不佳
手动事件侦听器重新注册
另一种解决方案是每次重新注册事件侦听器,这样回调总是从封闭范围中获取新状态:
function eventListener() {
// doesn't matter how often the listener is registered
setState(freshState => freshState + 1);
}
function eventListener() {
console.log(state);
}
useEffect(() => {
// register eventListener on each state update
return () => {
// unregister eventListener
};
}, [state]);
内置事件处理
除非事件侦听器注册在文档
,窗口
或其他事件目标不在当前组件的范围内,否则必须尽可能使用React自己的DOM事件处理,这样就不需要useEffect
:
<button onClick={eventListener} />
之前版本的答案建议使用可变状态,该状态适用于React 16.7.0-alpha版本中的初始useState
hook实现,但在最终React 16.8实现中不可行useState
目前只支持不可变状态。解决这个问题的更简洁的方法是创建一个我调用的钩子useStateRef
function useStateRef(initialValue) {
const [value, setValue] = useState(initialValue);
const ref = useRef(value);
useEffect(() => {
ref.current = value;
}, [value]);
return [value, setValue, ref];
}
您现在可以使用ref
作为状态值的参考。我的简短回答是,useState有一个简单的解决方案:
函数示例(){
常量[状态,设置状态]=使用状态(初始状态);
功能更新(更新){
//这可能是陈腐的
setState({…state,…updates});
//但您可以将函数传递给setState
setState(currentState=>({…currentState,…updates}));
}
//...
}
检查控制台,您会得到答案:
React Hook useffect缺少依赖项:“props.handleCardClick”。包括它或删除依赖项数组。(反应钩/详尽的DEP)
只需添加道具。HandleCard单击到依赖项数组,它就会正常工作。简短回答
此不会触发重新启动
此将触发渲染->将myvar放入[]
更改index.js
文件中的以下行后,按钮2
起作用
const [myvar, setMyvar] = useState('')
useEffect(() => {
setMyvar('foo')
}, []);
const [myvar, setMyvar] = useState('')
useEffect(() => {
setMyvar('foo')
}, [myvar]);
useEffect(() => {
ref.current.addEventListener("click", props.handleCardClick);
return () => {
ref.current.removeEventListener("click", props.handleCardClick);
};
- }, []);
+ });
// registers an event listener to component parent
React.useEffect(() => {
const parentNode = elementRef.current.parentNode
parentNode.addEventListener('mouseleave', handleAutoClose)
return () => {
parentNode.removeEventListener('mouseleave', handleAutoClose)
}
}, [handleAutoClose])