【问题标题】:Why does updating state using React Hooks, MomentJS object and interval/timeout not trigger re-render?为什么使用 React Hooks、MomentJS 对象和间隔/超时更新状态不会触发重新渲染?
【发布时间】:2019-08-30 14:48:33
【问题描述】:

我正在尝试使用 React-Native、React Hooks、MomentJS 和 (setTimeout/setInterval) 来制作倒数计时器。无论我尝试使用哪种方法,它都失败了。问题是组件永远不会重新渲染。

我尝试遵循官方的 React Hooks 文档,一些关于 Medium 的文章,例如 The Iceberg of React Hooks,但没有任何效果。

一种可能性是它需要对 MomentJS 对象进行深度克隆,但我猜这是一种低效的方法。

这是我尝试过的可重现示例之一。

const Timer = () => {
  const [time, setTime] = useState(moment.duration(30, 'seconds'))
  const intervalRef = useRef()

  useEffect(() => {
    intervalRef.current = setTimeout(() => {
      setTime(prevTime => prevTime.subtract(1, 'second'))
    }, 1000)

    return () => {
      clearInterval(intervalRef.current)
      intervalRef.current = null
    }
  })

  return (
    <View>
      {time.asSeconds()}
    </View>
  )

【问题讨论】:

  • 使用set*() 不应该改变对象(荒谬 - 在这种情况下,您甚至不需要调用setTime())。当状态/属性不改变时避免重新渲染是使 React 易于使用的基本优化之一。创建一个新的duration 对象的开销可以忽略不计,您可以使用clone() 快速完成。或者,您可以考虑使用number 而不是moment.Duration,并在渲染时创建一个新对象。另外注意不需要intervalRef,可以保存const ref = setTimeout(...); return () =&gt; clearInterval(ref);
  • 这很奇怪。我一直认为,为了在状态发生变化但对象相同时不重新渲染组件,您必须将其实现为纯组件
  • 状态实际上并没有改变,对对象的引用是一样的
  • @WillJenkins 在调用setState 的非功能组件中总是重新渲染。这就是为什么我很困惑,虽然现在我想它设置状态可能总是克隆状态

标签: javascript reactjs react-native react-hooks


【解决方案1】:

你是对的,它没有重新渲染,因为你的时刻对象在每个刻度上都是相同的(但发生了变异)。您可以通过在您的 setTime 更新程序中添加 .clone() 轻松使其工作:

const Timer = () => {
  const [time, setTime] = useState(moment.duration(30, "seconds"));
  const intervalRef = useRef();

  useEffect(() => {
    intervalRef.current = setTimeout(() => {
      setTime(prevTime => prevTime.clone().subtract(1, 'second'))
    }, 1000)

    return () => {
      clearInterval(intervalRef.current)
      intervalRef.current = null
    }
  })

  return <div>{time.asSeconds()}</div>;
};

这里的工作沙盒:https://codesandbox.io/s/gifted-euler-e8xg5

【讨论】:

    【解决方案2】:

    一种可能性是它需要对 MomentJS 对象进行深度克隆,但我猜这是一种低效的方法。

    没错。如果当前状态和之前的状态相等,React 不会重新渲染。您可以只将秒存储在状态中。

    而且你不需要那个参考。

    const Timer = () => {
      const [time, setTime] = useState(30 /*s*/)
    
      useEffect(() => {
        const timeout = setTimeout(() => {
          setTime(prevTime => prevTime - 1);
        }, 1000)
    
        return () => clearTimeout(timeout);
      }, [time])
    
      return (
       <View>
         {time}
       </View>
     );
    

    【讨论】:

    • 不是内存泄漏吗?如果每次“时间”更新时都设置超时,它会创建一个新的超时,不是吗?
    • @honza 确切地说,它会每秒创建一个新的超时。
    • 哦,对不起,我在脑子里用 setInterval 交换了 setTimout
    猜你喜欢
    • 2019-07-31
    • 1970-01-01
    • 2019-06-26
    • 2019-05-07
    • 2020-07-13
    • 2020-01-25
    • 2020-08-12
    • 1970-01-01
    • 2020-11-04
    相关资源
    最近更新 更多