简体   繁体   English

如何在useEffect中使用useState?

[英]How to use useState in useEffect?

I try to refacto my compoenent from react.component to hooks but I got some troubles.我尝试将我的组件从 react.component 重构为钩子,但我遇到了一些麻烦。 I don't really understand how to use my state offsetTop .我真的不明白如何使用我的 state offsetTop The value is set, or not when I need.该值已设置,或者在我需要时不设置。

First try:第一次尝试:

const [offsetTop, setOffsetTop] = useState();

useEffect(() => {
    setOffsetTop(window.pageYOffset + document.querySelector(".projectsContainer").getBoundingClientRect().top)
    console.log(offsetTop);
    window.addEventListener('scroll', handleScroll, true)
    return () => window.removeEventListener("scroll", handleScroll);
}, []);

I also tried to use it with a second method useEffect with [offsetTop] as a second parameters but my log showed up twice with undef and the value.我还尝试将它与第二种方法useEffect一起使用,并将[offsetTop]作为第二个参数,但我的日志显示了两次 undef 和值。 I understand why it shows twice, but still doesn't solve my problem, my condition does not work.我明白为什么它显示两次,但仍然没有解决我的问题,我的情况不起作用。

Second try:第二次尝试:

const [offsetTop, setOffsetTop] = useState();

useEffect(() => {
    setOffsetTop(window.pageYOffset + document.querySelector(".projectsContainer").getBoundingClientRect().top)
}, []);

useEffect(() => {
    console.log(offsetTop);
    window.addEventListener('scroll', handleScroll, true)
    return () => window.removeEventListener("scroll", handleScroll);
}, [offsetTop]);

handleScroll function:手柄滚动 function:

function handleScroll() {
    console.log(offsetTop);
    const scrolledY = window.scrollY
    if (scrolledY > offsetTop) console.log("works")
}

The same code as the First try: placed in componentDidMount() {} and using this works fine.与第First try:放置在componentDidMount() {}中并使用this可以正常工作。 So it is not the condition that is bad.所以不好的不是条件。 It's about when the value is set and how useState and useEffect works.这是关于何时设置值以及useStateuseEffect如何工作。

Doesn't your condition work in the first variant?您的条件在第一个变体中不起作用吗? That one looks correct, and console.log(offsetTop) in that case is not supposed to print the newly set value, but the previously set value.那个看起来是正确的,在这种情况下, console.log(offsetTop)不应该打印新设置的值,而是打印之前设置的值。 To log it like you want you need:要按照您的需要记录它:

const [offsetTop, setOffsetTop] = useState();

useEffect(() => {
    setOffsetTop(window.pageYOffset + document.querySelector(".projectsContainer").getBoundingClientRect().top)
    window.addEventListener('scroll', handleScroll, true)
    return () => window.removeEventListener("scroll", handleScroll);
}, []);

useEffect(() => {
    console.log(offsetTop);
}, [offsetTop]);

The problem with your second try is that it does the following:您第二次尝试的问题是它执行以下操作:

  • First render:第一次渲染:
    • 1st useEffect sets new offsetTop第一个 useEffect 设置新的offsetTop
    • 2nd useEffect fires with old offsetTop第二个 useEffect 用旧的offsetTop触发
  • offsetTop update causes re-render offsetTop更新导致重新渲染
    • 2nd useEffect does clean-up, removing the event listener第二个 useEffect 进行清理,删除事件侦听器
    • I guess, somewhere here browser propagates event, with your listener gone我猜,浏览器在某处传播事件,而你的听众不见了
    • 2nd useEffect fires second time, with new offsetTop , and sets your listener again (but it already missed the event) 2nd useEffect 第二次触发,使用新的offsetTop ,并再次设置你的监听器(但它已经错过了事件)

Ah... that's only a part of your problem.啊...这只是您问题的一部分。

The second part of your problem is that handleScroll() effectively captures offsetTop value from the render cycle where it was created and set as the listener.问题的第二部分是handleScroll()有效地从创建它并设置为侦听器的渲染周期中捕获offsetTop值。 You should use useRef() to keep handleScroll() in-sync with the current value (and if you don't need offsetTop for other reasons, you don't need to use the state at all. You can do:您应该使用useRef()使handleScroll()与当前值保持同步(如果您因为其他原因不需要offsetTop ,则根本不需要使用 state。您可以这样做:

const { current: heap } = useRef({
  offsetTop: 0,
});

useEffect(() => {
  function handleScroll() {
    console.log(heap.offsetTop);
    const scrolledY = window.scrollY
    if(scrolledY > heap.offsetTop) console.log("works")
  }
  heap.offsetTop = window.pageYOffset + document.querySelector(".projectsContainer").getBoundingClientRect().top;
  window.addEventListener('scroll', handleScroll, true)
  return () => window.removeEventListener("scroll", handleScroll);
  // The heap is guaranteed to be the same object across re-renders,
  // thus including it into dependencies below does not cause useEffect
  // to recompute. However, if you don't include it, and rely on ESLint
  // to check code errors, it will complain about heap not being there
  // as missing dependency.
}, [heap]);

This way, offsetTop is persistent across re-renders, and handleScroll() always uses the current value.这样, offsetTop在重新渲染中是持久的,并且handleScroll()始终使用当前值。 And if you need also re-render on offsetTop updates, then you can additionally use useState() to trigger re-renders (say to also inject offsetTop values into rendered components.如果您还需要在offsetTop更新上重新渲染,那么您还可以使用useState()来触发重新渲染(比如也将offsetTop值注入到渲染的组件中。

This strikes me as a case where you may not need state at all.这让我觉得你可能根本不需要 state。 Why not derive the value of offsetTop as needed?为什么不根据需要导出offsetTop的值呢?

useEffect(() => {
  function handleScroll() {
    const scrolledY = window.scrollY;
    const offsetTop = window.pageYOffset + document.querySelector(".projectsContainer").getBoundingClientRect().top;
    if(scrolledY > offsetTop) console.log("works")
  }

  window.addEventListener('scroll', handleScroll, true)
  return () => window.removeEventListener("scroll", handleScroll, true);
}, []);

However, maybe the idea is that you want to capture the initial value of offsetTop and then use it as things change.但是,也许这个想法是您想要捕获offsetTop的初始值,然后在事情发生变化时使用它。 In that case, I think your main problem is due to removeEventListener not being cleaned up correctly because you're not passing the same parameters as those given to addEventListener (missing true ).在这种情况下,我认为您的主要问题是由于removeEventListener没有被正确清理,因为您没有传递与addEventListener相同的参数(缺少true )。 Because of that, there is still an instance of handleScroll holding the initial value of offsetTop from the first render, and I think that's why you're seeing undefined being logged.正因为如此,仍然有一个handleScroll的实例保存了第一次渲染的offsetTop的初始值,我认为这就是为什么你看到undefined被记录的原因。

Wrong ❌错了❌

useEffect(() => {
  setOffsetTop(window.pageYOffset +
      document.querySelector(".projectsContainer").getBoundingClientRect().top;);
}, []);

useEffect(() => {
  // The first time this useEffect runs, the value of `offsetTop`
  // is still the initial value of undefined.
  function handleScroll() {
    console.log(offsetTop)
    const scrolledY = window.scrollY;
    if (scrolledY > offsetTop) console.log("works");
  }

  window.addEventListener("scroll", handleScroll, true);
  // This doesn't get removed correctly
  // because you don't pass the same parameters (missing `true`)
  return () => window.removeEventListener("scroll", handleScroll); 
}, [offsetTop]);

Right ✅没错✅

useEffect(() => {
  setOffsetTop(window.pageYOffset +
      document.querySelector(".projectsContainer").getBoundingClientRect().top;);
}, []);

useEffect(() => {
  function handleScroll() {
    const scrolledY = window.scrollY;
    if (scrolledY > offsetTop) console.log("works");
  }

  window.addEventListener("scroll", handleScroll, true);
  return () => window.removeEventListener("scroll", handleScroll, true);
}, [offsetTop]);

Reproduction: https://codesandbox.io/s/so-67247616-pl455转载: https://codesandbox.io/s/so-67247616-pl455

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

相关问题 如果消息不为null,useEffect会显示Snackbar,如何在其中使用useState? - useEffect to show snackbar if message is not null, and how to use useState with that? 如何使用 useEffect 的异步返回值作为 useState 中的默认值? - How use async return value from useEffect as default value in useState? 如何使用异步数据库调用通过 useState() 和 useEffect() 设置变量? - How to use an async database call to set a variable with useState() and useEffect()? 如何在 useEffect 中使用 useState,访问组件挂载时更新的 useState 值? - How to use useState inside useEffect, accessing the updated useState value on component mount? 如何正确组合 useState 和 useEffect - How to combine useState and useEffect properly 如何在 useState 和 useEffect 中设置初始状态 - How to set initial state in useState and useEffect 如何测试useState和useEffect反应钩子 - How to test useState and useEffect react hooks useEffect() 中的异步/等待:如何获取 useState() 值? - Async/Await in useEffect(): how to get useState() value? 使用带有数组参数的 useState 时,如何使用 useEffect 动态更新按钮? - When using useState with an array argument, how do you use useEffect to update a button dynamically? 我应该导入 useState 和 useEffect 还是可以使用 React.useState 和 React.useEffect? - Should I import useState and useEffect or is it ok to use React.useState and React.useEffect?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM