【问题标题】:useState acting out when trying to update with a setTimeout尝试使用 setTimeout 更新时 useState
【发布时间】:2020-01-17 10:32:13
【问题描述】:

我正在尝试使用 setTimeout 每 6 秒在 true 和 false 之间翻转一个 useState。我使用此状态从 div 中添加/删除类 - 这将导致 div 在 top: 0top: 100% 之间切换(transition 负责动画)。

更具体地说,我有一个 iPhone 包装图像,其中包含一个内容图像。这个想法是它会慢慢滚动到内容图像的底部,然后在 6 秒后(从它开始向下滚动的时间开始),它会开始向上滚动,无限循环。但是,它根本无法正常工作。

我已经使用 onClick 对其进行了测试,它完全符合我的预期。但是,使用 setTimeout 逻辑:

  • 状态总是 false - 即使我们将其设置为 true 然后记录它
  • 从 JSX 的角度来看,状态始终为 true - 始终添加类,这意味着状态为 true

当然不能真假,应该是翻转它的值。有人可以告诉我为什么它不起作用,或者告诉我为什么它会以这种奇怪的方式运行吗?

import React, { useState } from 'react';
import iphone from '../Images/iphone.png'

const Iphone = (props) => {
    const [isInitialised, setInitialised] = useState(false)
    const [animating, setAnimating] = useState(false)

    const startAnimation = () => {
        setAnimating(!animating); /* Even if I use `true`, it will log to the console as `false` */
        console.warn('animation change iphone!');
        console.warn(animating);
        console.warn(isInitialised); /* This also always logs as `false` */

        setTimeout(() => {
            startAnimation();
        }, 6000);
    }

    if (!isInitialised) {
        setInitialised(true);
        startAnimation();
    }

    return (
        <div className={`iphone align-mobile-center ${animating ? "iphone--animating" : ""}`} onClick={() => setAnimating(!animating)}>
            <img className="iphone__image" src={iphone} alt="An iPhone" />
            <div className="iphone__content">
                <img className="iphone__content-image" src={props.image} alt={props.alt} />
            </div>
        </div>
    )
}

export default Iphone;

我使用isInitialised,否则它似乎会陷入无限循环。

【问题讨论】:

  • 将 setTimeout 绑定到外部并在 setTimeout 函数内部设置状态
  • 记得清除超时,否则每次渲染都会重新创建。也许把它放在useEffect 里面?

标签: javascript reactjs


【解决方案1】:

你可以做这样的事情。 useEffect 与空数组作为 deps 所以它只会一次,你不需要 isInitialized 状态。

在 useEffect 中使用 setInterval,因此它将每 6 秒运行一次。

使用回调的方式设置状态,这样你总能得到正确的动画值。

import React, { useState } from 'react';
import iphone from '../Images/iphone.png'

const Iphone = (props) => {
    const [animating, setAnimating] = useState(false)

    useEffect(() => {
      const startAnimation = () => {
        setAnimating(v => !v);
      }
      const interval = setInterval(() => {
        startAnimation();
      }, 6000);
      return () => clearInterval(interval);
    }, []);

    return (
        <div className={`iphone align-mobile-center ${animating ? "iphone--animating" : ""}`} onClick={() => setAnimating(!animating)}>
            <img className="iphone__image" src={iphone} alt="An iPhone" />
            <div className="iphone__content">
                <img className="iphone__content-image" src={props.image} alt={props.alt} />
            </div>
        </div>
    )
}

export default Iphone;

【讨论】:

  • 只是一个旁注,但您可能应该将间隔分配给某个变量,然后在组件卸载时从useEffect 清除返回函数中的间隔
  • @Simon 不错。以明确的间隔更新答案。
【解决方案2】:

您可以使用 useEffect 并将 setTimeout 带到外面。

import React, { useState } from 'react';
import iphone from '../Images/iphone.png'

const Iphone = (props) => {
    const [isInitialised, setInitialised] = useState(false)
    const [animating, setAnimating] = useState(false)
    useEffect(()=>{
       startAnimation();
    },[]);
    const startAnimation = () => {
        setAnimating(!animating); 
        console.warn('animation change iphone!');
        console.warn(animating);
        console.warn(isInitialised);
    }

    setTimeout(() => {
      if (!isInitialised) {
        setInitialised(true);
        startAnimation();
      }
    }, 6000);



    return (
        <div className={`iphone align-mobile-center ${animating ? "iphone--animating" : ""}`} onClick={() => setAnimating(!animating)}>
            <img className="iphone__image" src={iphone} alt="An iPhone" />
            <div className="iphone__content">
                <img className="iphone__content-image" src={props.image} alt={props.alt} />
            </div>
        </div>
    )
}

export default Iphone;

【讨论】:

  • 如果我将startAnimation 移出isInitialised 支票,这将有效,非常感谢。令人担忧的是,如果我将它保存在 VSCode 中,ESLint 会将 startAnimation 添加到 useEffect 末尾的数组中,这会导致它一遍又一遍地快速调用它。我不确定为什么或如何在不禁用扩展程序的情况下解决此问题...
  • @DevMan eslint 为钩子制定规则,要求您在效果内部添加值作为依赖项。你能检查我的答案,看看它是否适合你
  • @AmitChauhan 我将useEffect 更新为useCallback,现在可以正常使用末尾的[startAnimation]
  • @AmitChauhan 我用你的答案更新了代码 - 需要将 startAnimating 添加到 useEffect 以使其立即运行(除了每 6 秒运行一次),但它可以工作并消除了需要为初始化状态。非常感谢
猜你喜欢
  • 2020-11-19
  • 1970-01-01
  • 2020-12-24
  • 2023-02-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-15
  • 1970-01-01
  • 2021-12-11
相关资源
最近更新 更多