简体   繁体   English

在滚动事件中侦听的函数中未更新的React钩子

[英]React hook not updated in function listened on scroll event

I have a function handleScroll that is listened on the scroll event. 我有一个在scroll事件上监听的函数handleScroll Thsi function must update isFetching (that starts false and must change the boolean value). Thsi函数必须更新isFetching (它开始为false并且必须更改布尔值)。

The function handleScroll is correctly listened, as the console.log shows. 正如console.log所示,正确监听函数handleScroll However, isFetching is always false. 但是, isFetching总是错误的。 It seems like setIsFetching is never read. 似乎从未读过setIsFetching Another option, I think, is like the eventListener freezes the first version of the handleScroll function. 我认为另一种选择就像eventListener冻结了handleScroll函数的第一个版本。

How can I do in order to update the hook in that function? 我该怎么做才能更新该函数中的钩子? Here is a simplified version of the code and the 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);

UPDATE UPDATE

I put an empty array as a second param in the useEffect because I want that the first param function only fires once on componentDidMount() 我把一个空数组作为useEffect的第二个参数,因为我希望第一个param函数只在componentDidMount()上触发一次

In order to listen the changes in your state from inside the useEffect callback (when you're not following any props update), you can save your state in a variable outside your component's scope, and using it instead of the state directly. 为了从useEffect回调内部监听状态的变化(当你没有跟踪任何道具更新时),你可以将状态保存在组件范围之外的变量中,并直接使用它而不是状态。

Here you have the code: 在这里你有代码:

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);

Add isFetching as a dependency to useEffect 添加isFetching作为isFetching的依赖useEffect

While i can't provide a deep explanation, I can say that you basically lied to React in useEffect when you said the effect doesn't depend on anything by providing an empty array of dependencies it's always good to pass all the variables that include in your effect. 虽然我无法提供深入的解释,但我可以说当你说效果不依赖于任何东西时,你基本上在React中useEffect了React,通过提供一个空的依赖数组,总是很好地传递包含在中的所有变量你的影响。

Also you create a new function every time the component re-render , to avoid this move the function inside of useEffect or wrap it inside useCallback which will not create re-create the function unless something in the array of dependencies changes 此外,每次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]
);

Or with useCallback 或者使用useCallback

const handleScroll = useCallback(
  debounce(() => {
    setIsFetching(prevState => !prevState);
    console.log({ isFetching });
  }, 300),
  [isFetching]
);

useEffect(
  () => {
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  },
  [isFetching]
);

complete guide to useEffect useEffect的完整指南

You have passed an empty array as a second argument to useEffect. 您已将空数组作为useEffect的第二个参数传递。 And this is what your key issue for not being called useEffect again after it is run. 这就是你在运行后不再被称为useEffect的关键问题。

To solve the issue, just don't pass the second argument. 要解决这个问题,请不要传递第二个参数。

 useEffect(() => {
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  });

Or, call the useEffect whenever the property being changed: 或者,每当要更改的属性时调用useEffect:

useEffect(() => {
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  }, [isFetching]); // re-run useEffect on isFetching changed

This is similar to what we do in componentDidUpdate: 这与我们在componentDidUpdate中的操作类似:

if (prevState.count !== this.state.count) {
  // do the stuff

For more detail, see the documentation itself. 有关更多详细信息,请参阅文档本身。

A note from the docs: 来自文档的说明:

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. 这告诉React你的效果不依赖于来自props或state的任何值,所以它永远不需要重新运行。 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. 如果传递一个空数组([]),效果中的props和state将始终具有其初始值。 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. 虽然传递[]作为第二个参数更接近熟悉的componentDidMount和componentWillUnmount心理模型,但通常有更好的解决方案来避免经常重新运行效果。 Also, don't forget that React defers running useEffect until after the browser has painted, so doing extra work is less of a problem. 此外,不要忘记React推迟运行useEffect直到浏览器绘制完成后,所以做额外的工作不是问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM