Cập nhật thành phần React mỗi giây


114

Tôi đã chơi với React và có thành phần thời gian sau đây chỉ hiển thị Date.now() trên màn hình:

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;

Cách tốt nhất để khiến thành phần này cập nhật mỗi giây để vẽ lại thời gian từ góc độ React là gì?

Câu trả lời:


150

Bạn cần sử dụng setIntervalđể kích hoạt thay đổi, nhưng bạn cũng cần xóa bộ đếm thời gian khi thành phần ngắt kết nối để tránh nó để lại lỗi và rò rỉ bộ nhớ:

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

2
Nếu bạn muốn có một thư viện mà đóng gói loại điều, tôi đã thực hiệnreact-interval-rerender
Andy

Tôi đã thêm phương thức setInterval của mình trong hàm tạo của mình và điều đó hoạt động tốt hơn.
aqteifan

89

Đoạn mã sau là một ví dụ được sửa đổi từ trang web React.js.

Mã gốc có sẵn tại đây: 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
Vì lý do nào đó, tôi nhận được this.updater.enqueueSetState không phải là một lỗi chức năng khi sử dụng cách tiếp cận này. callback setInterval được liên kết đúng với thành phần này.
hói đầu

16
Đối với kẻ ngốc như tôi: không đặt tên cho bộ đếm thời gian updatervì nó tàn tích cập nhật chu kỳ
baldrs

3
Với ES6, tôi cần thay đổi một dòng như this.interval = setInterval (this.tick.bind (this), 1000);
Kapil

Bạn có thể thêm liên kết đến url tham chiếu không? Cảm ơn ~
frederj

Tôi đã thêm một phương thức định dạng và một thuộc tính "constructor" để có thêm tính mạnh mẽ.
Ông Polywhirl

15

@Waisky đề xuất:

Bạn cần sử dụng setIntervalđể kích hoạt thay đổi, nhưng bạn cũng cần xóa bộ đếm thời gian khi thành phần ngắt kết nối để tránh nó để lại lỗi và rò rỉ bộ nhớ:

Nếu bạn muốn làm điều tương tự, hãy sử dụng Hooks:

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

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

Về các ý kiến:

Bạn không cần phải chuyển bất cứ thứ gì vào bên trong [] . Nếu bạn chuyển timevào trong dấu ngoặc, điều đó có nghĩa là chạy hiệu ứng mỗi khi giá trị timethay đổi, tức là, nó sẽ gọi một giá trị mới setIntervalmỗi lần timethay đổi, đó không phải là thứ chúng ta đang tìm kiếm. Chúng tôi muốn chỉ gọi setIntervalmột lần khi thành phần được gắn kết và sau đó setIntervalgọi setTime(Date.now())mỗi 1000 giây. Cuối cùng, chúng tôi gọiclearInterval khi thành phần được ngắt kết nối.

Lưu ý rằng thành phần được cập nhật, dựa trên cách bạn đã sử dụng timetrong đó, mỗi khi giá trị củatime thay đổi. Đó là không có gì để làm với việc đưa timevào []các useEffect.


1
Tại sao bạn lại truyền một mảng trống ( []) làm đối số thứ hai useEffect?
Mekeor Melire

Các Phản ứng tài liệu trên móc hiệu lực cho chúng ta biết: “Nếu bạn muốn chạy một tác dụng và làm sạch nó lên một lần duy nhất (trên gắn kết và unmount), bạn có thể vượt qua một mảng rỗng ([]) như là một đối số thứ hai.” Nhưng OP muốn hiệu ứng chạy mỗi giây, tức là lặp lại, tức là không chỉ một lần.
Mekeor Melire

Sự khác biệt mà tôi nhận thấy nếu bạn vượt qua [thời gian] trong mảng, nó sẽ tạo và phá hủy thành phần mỗi khoảng thời gian. Nếu bạn vượt qua mảng trống [], nó sẽ tiếp tục làm mới thành phần và chỉ ngắt kết nối nó khi bạn rời khỏi trang. Tuy nhiên, trong cả hai trường hợp, để nó hoạt động, tôi phải thêm key = {time} hoặc key = {Date.now ()} để nó thực sự kết xuất.
Teebu

8

Trong componentDidMountphương thức vòng đời của thành phần , bạn có thể đặt khoảng thời gian để gọi một hàm cập nhật trạng thái.

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

4
Đúng, nhưng như câu trả lời hàng đầu gợi ý, hãy nhớ xóa thời gian để tránh rò rỉ bộ nhớ: componentWillUnmount () {clearInterval (this.interval); }
Alexander Falk

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

0

Vì vậy, bạn đã đi đúng hướng. Bên trong của componentDidMount()bạn, bạn có thể đã hoàn thành công việc bằng cách triển khai setInterval()để kích hoạt thay đổi, nhưng hãy nhớ cách cập nhật trạng thái thành phần là thông qua setState(), vì vậy bên trong của componentDidMount()bạn, bạn có thể thực hiện điều này:

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

Ngoài ra, bạn sử dụng Date.now()cái nào hoạt động, với cách componentDidMount()triển khai tôi đã đưa ra ở trên, nhưng bạn sẽ nhận được một tập hợp dài cập nhật các con số khó chịu mà con người không thể đọc được, nhưng về mặt kỹ thuật, đó là thời gian cập nhật mỗi giây tính bằng mili giây kể từ ngày 1 tháng 1 năm 1970, nhưng chúng tôi muốn làm cho thời gian này có thể đọc được để làm sao chúng ta con người thời gian đọc, vì vậy ngoài việc học tập và thực hiện các setIntervalbạn muốn tìm hiểu về new Date()toLocaleTimeString()và bạn sẽ thực hiện nó như vậy:

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

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

Lưu ý rằng tôi cũng đã gỡ bỏ constructor()chức năng, bạn không nhất thiết phải cần đến nó, trình tái cấu trúc của tôi tương đương 100% với việc khởi tạo trang web với constructor()chức năng.


0

Do những thay đổi trong React V16 mà componentWillReceiveProps () không được dùng nữa, đây là phương pháp mà tôi sử dụng để cập nhật một thành phần. Lưu ý rằng ví dụ dưới đây là trong Typescript và sử dụng phương thức getDerivedStateFromProps tĩnh để lấy trạng thái ban đầu và trạng thái cập nhật bất cứ khi nào Props được cập nhật.

    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>;
  }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.