每秒更新React组件

194

我一直在尝试使用React,并拥有以下时间组件,它只会将Date.now()渲染到屏幕上:


import React, { Component } from 'react';

class TimeComponent extends Component {
  constructor(props){
    super(props);
    this.state = { time: Date.now() };
  }
  render(){
    return(
      <div> { this.state.time } </div>
    );
  }
  componentDidMount() {
    console.log("TimeComponent Mounted...")
  }
}

export default TimeComponent;

从 React 的角度来看,每秒更新组件以重新绘制时间的最佳方法是什么?

9个回答

226

你需要使用setInterval来触发更改,但也需要在组件卸载时清除计时器,以防止出现错误和内存泄漏:

需要在组件卸载前使用clearInterval来清除定时器。

componentDidMount() {
  this.interval = setInterval(() => this.setState({ time: Date.now() }), 1000);
}
componentWillUnmount() {
  clearInterval(this.interval);
}

3
如果您想要一个封装了这种功能的库,我制作了react-interval-rerender - Andy
我将我的setInterval方法添加到了构造函数中,这样效果更好。 - aqteifan

125

@Waisky建议:

你需要使用setInterval触发更改,但也需要在组件卸载时清除计时器,以防止出现错误和内存泄漏:

如果您想使用Hooks做同样的事情:

const [time, setTime] = useState(Date.now());

useEffect(() => {
  const interval = setInterval(() => setTime(Date.now()), 1000);
  return () => {
    clearInterval(interval);
  };
}, []);

关于评论:

[]中不需要传递任何内容。如果你将time放在方括号中,那么它的意思是每次time的值变化时运行该效果,也就是每次都会调用新的setInterval而不是我们想要的。我们只希望在组件挂载时调用一次setInterval,然后setInterval每1000秒调用setTime(Date.now())。最后,在组件卸载时调用clearInterval

请注意,组件根据您在其中使用time的方式进行更新,每当time的值发生更改时都会更新。这与将time放在useEffect[]中无关。


1
你为什么在 useEffect 的第二个参数处传递一个空数组 ([])? - Mekeor Melire
2
React文档中关于effect hook的说明告诉我们:“如果你想要运行一次效果并在挂载和卸载时清理它,你可以将一个空数组([])作为第二个参数传递。”但是OP希望每秒运行一次效果,即重复出现,而不仅仅是一次。 - Mekeor Melire
1
我注意到的区别是,如果你在数组中传递 [time],它会在每个时间间隔内创建和销毁组件。如果你传递空数组 [],它将保持刷新组件,并且只有在离开页面时才卸载它。但是,在任何情况下,为了使其工作,我都必须添加 key={time} 或 key={Date.now()} 才能让它实际重新渲染。 - Teebu

97
以下代码是从React.js网站修改的示例。
原始代码可在此处找到:https://reactjs.org/#a-simple-component
class Timer extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      seconds: parseInt(props.startTimeInSeconds, 10) || 0
    };
  }

  tick() {
    this.setState(state => ({
      seconds: state.seconds + 1
    }));
  }

  componentDidMount() {
    this.interval = setInterval(() => this.tick(), 1000);
  }

  componentWillUnmount() {
    clearInterval(this.interval);
  }

  formatTime(secs) {
    let hours   = Math.floor(secs / 3600);
    let minutes = Math.floor(secs / 60) % 60;
    let seconds = secs % 60;
    return [hours, minutes, seconds]
        .map(v => ('' + v).padStart(2, '0'))
        .filter((v,i) => v !== '00' || i > 0)
        .join(':');
  }

  render() {
    return (
      <div>
        Timer: {this.formatTime(this.state.seconds)}
      </div>
    );
  }
}

ReactDOM.render(
  <Timer startTimeInSeconds="300" />,
  document.getElementById('timer-example')
);

1
使用这种方法时,我不知道为什么会出现this.updater.enqueueSetState不是一个函数的错误。setInterval的回调函数已正确绑定到组件的this对象上。 - baldrs
33
对于像我这样的白痴:不要将计时器命名为“更新程序”,因为它会破坏更新周期。 - baldrs
3
使用ES6,我需要更改这一行代码:interval = setInterval(this.tick.bind(this), 1000); - PapaDiHatti
你能添加引用链接到URL中吗?谢谢~ - frederj
我添加了一个格式化方法和一个“构造函数”属性以增强鲁棒性。 - Mr. Polywhirl

9
在组件的componentDidMount生命周期方法中,您可以设置一个间隔来调用一个更新状态的函数。
 componentDidMount() {
      setInterval(() => this.setState({ time: Date.now()}), 1000)
 }

4
没问题,但是正如最佳答案所建议的那样,记得清除定时器以避免内存泄漏:componentWillUnmount() { clearInterval(this.interval); } - Alexander Falk

5
class ShowDateTime extends React.Component {
   constructor() {
      super();
      this.state = {
        curTime : null
      }
    }
    componentDidMount() {
      setInterval( () => {
        this.setState({
          curTime : new Date().toLocaleString()
        })
      },1000)
    }
   render() {
        return(
          <div>
            <h2>{this.state.curTime}</h2>
          </div>
        );
      }
    }

3

我个人更喜欢setTimeout而不是setInterval,但在基于类的组件中没有找到解决方案。您可以在基于类的组件中使用类似以下的内容:

基于类的组件和setInterval

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      date: new Date()
    };
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      this.state.date.toLocaleTimeString()
    );
  }
}

ReactDOM.render( 
  <Clock / > ,
  document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="app" />

基于函数的组件和 setInterval:

https://codesandbox.io/s/sweet-diffie-wsu1t?file=/src/index.js

基于函数的组件和 setTimeout:

https://codesandbox.io/s/youthful-lovelace-xw46p


0

即使使用setTimeout而不是setInterval也可以实现这一点。由于useState重新渲染组件,它将一遍又一遍地调用setTimeout。 下面是我样例组件,每秒更新计时器。如果我在这里犯了任何错误,请告诉我。

import React, { useEffect, useState } from 'react'

export default function Footer() {
    const [seconds, setSeconds] = useState((new Date()).getSeconds());

    function GetTime() {
        setSeconds((new Date()).getSeconds());
        console.count(seconds);
    }

    setTimeout(() => {
        console.log("Footer Rendered");
        GetTime();
    }, 1000);

    return (
        <footer>
            <h2>Test Footer</h2>
            <p>Copyright &copy; {seconds}</p>
        </footer>
    )
}


0

所以你走在了正确的轨道上。在你的componentDidMount()中,你可以通过实现setInterval()来触发更改,但是请记住更新组件状态的方法是通过setState(),因此在你的componentDidMount()中,你可以这样做:

componentDidMount() {
  setInterval(() => {
   this.setState({time: Date.now()})    
  }, 1000)
}

此外,您使用了Date.now(),它可以与我上面提供的componentDidMount()实现一起工作,但是您将得到一长串难以阅读的数字更新,尽管从技术上讲,它是自1970年1月1日以来每秒毫秒更新的时间,但我们希望将这个时间变得可读,就像人类读取时间一样,因此除了学习和实现setInterval之外,您还需要学习new Date()toLocaleTimeString(),并且您可以这样实现:

class TimeComponent extends Component {
  state = { time: new Date().toLocaleTimeString() };
}

componentDidMount() {
  setInterval(() => {
   this.setState({ time: new Date().toLocaleTimeString() })    
  }, 1000)
}

注意我还删除了constructor()函数,你不一定需要它,我的修改与使用constructor()函数初始化站点完全等价。


0
由于React V16中componentWillReceiveProps()已被弃用,因此这是我用于更新组件的方法论。 请注意,下面的示例是使用TypeScript编写的,并使用静态getDerivedStateFromProps方法获取初始状态和每当Props更新时更新的状态。
    class SomeClass extends React.Component<Props, State> {
  static getDerivedStateFromProps(nextProps: Readonly<Props>): Partial<State> | null {
    return {
      time: nextProps.time
    };
  }

  timerInterval: any;

  componentDidMount() {
    this.timerInterval = setInterval(this.tick.bind(this), 1000);
  }

  tick() {
    this.setState({ time: this.props.time });
  }

  componentWillUnmount() {
    clearInterval(this.timerInterval);
  }

  render() {
    return <div>{this.state.time}</div>;
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接