我有一个功能handleScroll
,它在滚动事件中被监听。此函数必须更新isFetching
(以false开头,并且必须更改布尔值)。
功能handleScroll
已正确监听,如console.log
所示。但是,isFetching
始终为假。
似乎从未读过setIsFetching
。我认为,另一种选择就像eventListener冻结了handleScroll函数的第一个版本。
我该怎么做才能更新该函数中的钩子? 这是代码和codesandbox的简化版本:
/* <div id='root'></div> */
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
const debounce = (func, wait, immediate) => {
let timeout;
return function() {
const context = this;
const args = arguments;
clearTimeout(timeout);
timeout = setTimeout(() => {
timeout = null;
if (!immediate) func.apply(context, args);
}, wait);
if (immediate && !timeout) func.apply(context, args);
};
};
const App = () => {
const [isFetching, setIsFetching] = useState(false);
const handleScroll = debounce(() => {
setIsFetching(!isFetching);
console.log({ isFetching });
}, 300);
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
return <div style={{ height: "1280px" }}>Hello world</div>;
};
const root = document.getElementById("root");
if (root) ReactDOM.render(<App />, root);
更新
我将一个空数组作为第二个参数放在useEffect
中,因为我希望第一个参数函数仅在componentDidMount()上触发一次
答案 0 :(得分:1)
为了从useEffect
回调内部监听状态变化(当您不进行任何道具更新时),可以将状态保存在组件范围之外的变量中,然后而不是直接使用它。
这里有代码:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
const debounce = (func, wait, immediate) => {
let timeout;
return function() {
const context = this;
const args = arguments;
clearTimeout(timeout);
timeout = setTimeout(() => {
timeout = null;
if (!immediate) func.apply(context, args);
}, wait);
if (immediate && !timeout) func.apply(context, args);
};
};
let isFetchingState;
const App = () => {
const [isFetching, setIsFetching] = useState(false);
isFetchingState = isFetching;
const handleScroll = debounce(() => {
setIsFetching(!isFetchingState);
console.log({ isFetchingState });
}, 300);
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
return <div style={{ height: "1280px" }}>Hello world</div>;
};
const root = document.getElementById("root");
if (root) ReactDOM.render(<App />, root);
答案 1 :(得分:1)
将isFetching
添加为对useEffect
的依赖项
虽然我无法提供深入的解释,但我可以说您基本上是在useEffect
中对React撒谎,因为您说效果不提供任何依赖关系,因为它提供了空的依赖关系数组,传递所有包含在您的效果中的变量。
还要在每次组件re-render
时创建一个新函数,以避免将此函数移到useEffect
内或将其包装在useCallback
内,这将不会重新创建该函数除非依赖项数组中的某些内容发生改变
useEffect(
() => {
const handleScroll = debounce(() => {
setIsFetching(prevState => !prevState);
console.log({ isFetching });
}, 300);
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
},
[isFetching]
);
const handleScroll = useCallback(
debounce(() => {
setIsFetching(prevState => !prevState);
console.log({ isFetching });
}, 300),
[isFetching]
);
useEffect(
() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
},
[isFetching]
);
答案 2 :(得分:1)
您已经传递了一个空数组作为useEffect的第二个参数。这就是您在运行后不再被称为useEffect的关键问题。
要解决此问题,只需不要传递第二个参数。
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
});
或者,只要更改属性,就调用useEffect:
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, [isFetching]); // re-run useEffect on isFetching changed
这类似于我们在componentDidUpdate中所做的事情:
if (prevState.count !== this.state.count) {
// do the stuff
有关更多详细信息,请参见documentation本身。
文档中的注释:
如果要运行效果并仅将其清理一次(在挂载和卸载时),则可以将空数组([])作为第二个参数传递。这告诉React,您的效果不依赖于道具或状态的任何值,因此它不需要重新运行。这不是特殊情况,它直接取决于依赖项数组始终如何工作。
如果传递一个空数组([]),则效果内的道具和状态将始终具有其初始值。尽管将[]作为第二个参数传递给了更熟悉的componentDidMount和componentWillUnmount心智模型,但通常会有更好的解决方案来避免过于频繁地重新运行效果。另外,别忘了React在浏览器绘制完成之后才推迟useEffect的运行,因此进行额外的工作不会有什么问题。