我有一个功能组件
import React, { useState } from 'react'
export default function AboutUs() {
const [counter,setCounter] = useState(0);
const clicked = () => {
setCounter(counter+1);
}
return (
<div>
<p>you clicked {counter} times</p>
<button onClick={clicked}>Click</button>
</div>
)
}
这没有任何问题,而且效果很好。但是当我删除按钮元素中的 onClick 并添加事件侦听器时:
import React, { useEffect, useState } from 'react'
export default function AboutUs() {
const [counter,setCounter] = useState(0);
useEffect(()=>{
document.getElementById('test').addEventListener('click',clicked);
},[])
const clicked = () => {
setCounter(counter+1);
}
return (
<div>
<p>you clicked {counter} times</p>
<button id="test">Click</button>
</div>
)
}
点击一次后计数器值将是1然后停止! counter 不再被 setCounter 改变! 这有什么问题? 感谢您的帮助。
答案 0 :(得分:7)
我有一个功能组件
你没有。
您的组件跟踪违反函数式编程原则的状态,即函数应该没有副作用。
您有一个功能组件。
计数器不再被 setCounter 改变!
事件处理程序附加在 useEffect
钩子中。
当组件第一次挂载以及任何依赖项发生变化时,就会调用该钩子。
依赖项列表是 []
所以依赖项永远不会改变。
每次调用(我们刚刚建立的只有一次)它会创建一个函数,closes over counter
常量(最初设置为 0
)
每次点击元素时,您都会得到 setCounter(counter+1);
,其中 counter
始终为 0
,因为这是关闭的常量的值。
(即使更改状态会触发重新渲染,从而创建具有相同名称的不同常量)。
要使用该方法执行此操作,您通常会执行以下操作:
useEffect(()=>{
document.getElementById('test').addEventListener('click',clicked);
return () => document.getElementById('test').removeEventListener('click', 'clicked');
},[clicked])
因此,每次函数更改时,它都会清除先前的事件侦听器并添加新的事件侦听器(已关闭新常量)
但是直接 DOM 操作是脆弱的,并且会抛弃 React 的主要优势。所以不要那样做。使用 onClick={click}
方法,这就是 React 的工作原理。
答案 1 :(得分:1)
这里有两个问题:
useEffect
依赖项未指定useEffect
没有清理处理程序
更新您的代码会得到如下结果:useEffect(() => {
const element = document.getElementById('test');
element.addEventListener('click', clicked);
return () => { element.removeEventListener('click', clicked) };
}, [clicked]);
但仍然存在一个问题:clicked
函数引用在每次渲染时都会发生变化,这意味着 useEffect
将在每次渲染时移除/添加侦听器。
为避免这种情况,您应该将点击的处理程序包装在具有适当依赖项的 useCallback
中,如下所示:
const clicked = useCallback(() => {
setCounter(counter + 1);
}, [counter]);