在状态更新时使用 eventListener 进行指数重新渲染

Exponential re-rendering with eventListener on state update

我正在尝试在“keydown”上使用 eventListener 来更新我的状态(一个名为 showMenu 的布尔值)。 我已将它放在 useEffect 上,但它无法正常工作,我不明白为什么... 如果我在 useEffect 的末尾将 showMenu 放在数组上,它会以指数方式重新呈现组件。 如果我不把 showMenu 放在 useEffect 的末尾,那么 showMenu 只会更新一次(从 false 到 true)

这里是指数级重新渲染的代码

const [showMenu, setShowMenu] = useState(false);


useEffect(() => {
    window.addEventListener("keydown", (e) => handleMenu(e));
    return () => {
        window.removeEventListener("keydown", (e) => handleMenu(e));
    };
}, [showMenu]);

const handleMenu = (e) => {
    if (e.key == "m") {
        setShowMenu(!showMenu);
    }
};

removeEventListener 接受一个函数 reference 但你每次都在创建一个匿名函数:

useEffect(() => {
    window.addEventListener("keydown", handleMenu);
    return () => {
        window.removeEventListener("keydown", handleMenu);
    };
}, [showMenu]);

通过删除匿名函数包装器(又名减少 eta),addEventListener 和 removeEventListener 获得相同的函数引用。

或者,如果您只支持现代浏览器并且想忽略函数引用的概念,addEventListener 支持 AbortSignals:

useEffect(() => {
    const ac = new AbortController();
    window.addEventListener("keydown", (e) => handleMenu(e), { signal: ac.signal });
    return () => ac.abort();
}, [showMenu]);

附带说明一下,确实没有理由将 showMenu 传递到函数的依赖列表中,我不会传递它。

好的,刚想通了。 useEffect 方法不是问题所在。逻辑很好。 问题出在我需要直接更改为 handleMenu(e) => handleMenu(e) 上。 见下文:

const [showMenu, setShowMenu] = useState(false);

const handleMenu = (e) => {
    if (e.key == "m") {
        setShowMenu(!showMenu);
    }
};

useEffect(() => {
    window.addEventListener("keydown", handleMenu);
    return () => {
        window.removeEventListener("keydown", handleMenu);
    };
}, [showMenu]);

感谢 Benjamin 给我答案:我需要传递我的 handleMenu 函数引用而不是使用 (e) => 语法,它每次都根据我的 handleMenu 函数创建一个新函数,它解释了指数渲染: )