Но меня беспокоит, будут ли проблемы с производительностью, если мы будем запускать несколько обновлений состояния на постоянной основе?
Не должно быть, вы говорите об обновлении каждую секунду или около того, что не так уж и много.
Обязательно не доверяйте интервалу, поскольку фактическое время между тем, когда вы планируете таймер и когда он срабатывает, может широко варьироваться в зависимости от того, что еще происходит. Поэтому обязательно всегда рассчитывайте время, оставшееся от начального начального времени , а не от суммирования времени, которое вы дали таймеру. Например:
timerCallback() {
this.setState({remaining: Date.now() - this.timerStarted});
}
или аналогичный.
Вот очень грубый пример того, почему вы всегда вспоминаете c, а не вычитаете интервал из оставшегося времени:
function zeroPad(x, width) {
return String(x).padStart(width, "0");
}
class Time extends React.Component {
render() {
let time = Math.round(this.props.time / 1000);
const seconds = time % 60;
time -= seconds;
const minutes = Math.floor(time / 60) % 60;
time -= minutes * 60;
const hours = Math.floor(time / 3600);
return [
<span>{zeroPad(hours, 2)}</span>,
":",
<span>{zeroPad(minutes, 2)}</span>,
":",
<span>{zeroPad(seconds, 2)}</span>
];
}
}
class BadTimer extends React.Component {
constructor(props) {
super(props);
this.interval = 1000;
this.state = {
running: false,
remaining: 0,
actualDuration: 0
};
this.start = this.start.bind(this);
this.timerCallback = this.timerCallback.bind(this);
}
componentDidMount() {
if (this.props.autoStart) {
this.start();
}
}
start() {
this.setState(({running}) => {
if (!running) {
this.started = Date.now(); // Just so we can show actual duration later
setTimeout(this.timerCallback, this.interval);
return {running: true, remaining: this.props.length};
}
});
}
timerCallback() {
this.setState(({running, remaining}) => {
if (running) {
// Don't do this
remaining = Math.max(remaining - this.interval, 0);
if (remaining <= 500) {
return {running: false, remaining, actualDuration: Date.now() - this.started};
}
setTimeout(this.timerCallback, this.interval);
return {remaining};
}
});
}
render() {
const {running, remaining, actualDuration} = this.state;
return (
<span>
{running ? <Time time={remaining} /> : <input type="button" value="Start" onClick={this.start} />}
{actualDuration ? <span> Actual duration: <Time time={actualDuration} /></span> : false}
</span>
);
}
}
class GoodTimer extends React.Component {
constructor(props) {
super(props);
this.interval = 1000;
this.state = {
started: 0,
update: 0,
actualDuration: 0
};
this.start = this.start.bind(this);
this.timerCallback = this.timerCallback.bind(this);
}
componentDidMount() {
if (this.props.autoStart) {
this.start();
}
}
start() {
this.setState(({started, update}) => {
if (!started) {
setTimeout(this.timerCallback, this.interval);
started = Date.now();
return {started};
}
});
}
timerCallback() {
// Do this instead
this.setState(({started, update}) => {
if (started) {
const remaining = this.getRemaining(started);
if (remaining <= 500) {
return {started: 0, actualDuration: Date.now() - started};
}
setTimeout(this.timerCallback, this.interval);
return {update: update + 1};
}
});
}
getRemaining(started) {
return this.props.length - (Date.now() - started);
}
render() {
const {started, actualDuration} = this.state;
const remaining = this.getRemaining(started);
return (
<span>
{started ? <Time time={remaining} /> : <input type="button" value="Start" onClick={this.start} />}
{actualDuration ? <span> Actual duration: <Time time={actualDuration} /></span> : false}
</span>
);
}
}
ReactDOM.render(
<div>
<div>
<label>Bad:</label><BadTimer autoStart={true} length={15000} />
</div>
<div>
<label>Good:</label><GoodTimer autoStart={true} length={15000} />
</div>
</div>,
document.getElementById("root")
);
// Throw some delays in
for (let n = 3000; n < 12000; n += 3000) {
setTimeout(() => {
console.log("Thread is busy...");
const busy = Date.now();
setTimeout(() => {
// Do something that takes some time
const stop = Date.now() + (Math.random() * 2000) + 700;
while (Date.now() < stop) {
// Loop
}
console.log(`...done, was busy ${Date.now() - busy}ms`);
}, 0);
}, n);
}
body {
font-size: 16px;
}
label {
width: 48px;
display: inline-block;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.11.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script>