在滚动事件上侦听的函数中未更新 React 挂钩
React hook not updated in function listened on scroll event
我有一个函数 handleScroll
在滚动事件上侦听。 Thsi 函数必须更新 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()
上触发一次
为了从 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);
添加 isFetching
作为对 useEffect
的依赖
虽然我无法提供深入的解释,但我可以说你在 useEffect
中基本上是在欺骗 React,因为你说效果不依赖于任何东西,它总是提供一个空的依赖数组很高兴传递您的效果中包含的所有变量。
另外,每次组件 re-render
时都会创建一个新函数,以避免将函数移动到 useEffect
内或将其包装在 useCallback
内,这样不会创建 re-create 函数,除非依赖项数组中的某些内容发生变化
useEffect(
() => {
const handleScroll = debounce(() => {
setIsFetching(prevState => !prevState);
console.log({ isFetching });
}, 300);
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
},
[isFetching]
);
或者用useCallback
const handleScroll = useCallback(
debounce(() => {
setIsFetching(prevState => !prevState);
console.log({ isFetching });
}, 300),
[isFetching]
);
useEffect(
() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
},
[isFetching]
);
您已将一个空数组作为第二个参数传递给 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 本身。
来自文档的注释:
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.
If you pass an empty array ([]), the props and state inside the effect will always have their initial values. While passing [] as the second argument is closer to the familiar componentDidMount and componentWillUnmount mental model, there are usually better solutions to avoid re-running effects too often. Also, don’t forget that React defers running useEffect until after the browser has painted, so doing extra work is less of a problem.
我有一个函数 handleScroll
在滚动事件上侦听。 Thsi 函数必须更新 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()
为了从 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);
添加 isFetching
作为对 useEffect
虽然我无法提供深入的解释,但我可以说你在 useEffect
中基本上是在欺骗 React,因为你说效果不依赖于任何东西,它总是提供一个空的依赖数组很高兴传递您的效果中包含的所有变量。
另外,每次组件 re-render
时都会创建一个新函数,以避免将函数移动到 useEffect
内或将其包装在 useCallback
内,这样不会创建 re-create 函数,除非依赖项数组中的某些内容发生变化
useEffect(
() => {
const handleScroll = debounce(() => {
setIsFetching(prevState => !prevState);
console.log({ isFetching });
}, 300);
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
},
[isFetching]
);
或者用useCallback
const handleScroll = useCallback(
debounce(() => {
setIsFetching(prevState => !prevState);
console.log({ isFetching });
}, 300),
[isFetching]
);
useEffect(
() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
},
[isFetching]
);
您已将一个空数组作为第二个参数传递给 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 本身。
来自文档的注释:
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.
If you pass an empty array ([]), the props and state inside the effect will always have their initial values. While passing [] as the second argument is closer to the familiar componentDidMount and componentWillUnmount mental model, there are usually better solutions to avoid re-running effects too often. Also, don’t forget that React defers running useEffect until after the browser has painted, so doing extra work is less of a problem.