Как сделать таймер без проблем с производительностью - PullRequest
0 голосов
/ 29 февраля 2020

Когда пользователь нажимает на конкретную кнопку, таймер должен работать, чтобы вычислить, как долго пользователь выполняет задачу. Таймер должен быть в формате ниже

HH : MM : SS
00 : 00 : 00

Таймер должен начинаться с 0, заканчиваться через 1 час. Насколько мне известно, это может быть достигнуто с помощью нескольких обновлений состояния и setInterval, setTimeout, clearInterval. Но меня беспокоит, возникнут ли какие-либо проблемы с производительностью, если мы будем запускать несколько обновлений состояния на постоянной основе? Спасибо!

1 Ответ

1 голос
/ 29 февраля 2020

Но меня беспокоит, будут ли проблемы с производительностью, если мы будем запускать несколько обновлений состояния на постоянной основе?

Не должно быть, вы говорите об обновлении каждую секунду или около того, что не так уж и много.

Обязательно не доверяйте интервалу, поскольку фактическое время между тем, когда вы планируете таймер и когда он срабатывает, может широко варьироваться в зависимости от того, что еще происходит. Поэтому обязательно всегда рассчитывайте время, оставшееся от начального начального времени , а не от суммирования времени, которое вы дали таймеру. Например:

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>
...