第一个SO帖子,所以任何提示都会有所帮助。
我目前正在将有状态的计时器组件转换为使用钩子,但是在 setInterval 或 setTimeout 中访问当前状态值时遇到问题。作为一个类组件,我能够轻松访问最新的状态属性值,但是由于 setInterval/timeout 中的闭包,当它们在 setInterval/setTimeout 之外设置时,我无法访问最新的钩子值(在暂停/播放 onClick 中(。
import React, { useState, useEffect, useRef } from 'react';
import { Link } from 'react-router-dom';
const Timer = (props) => {
let [previousTime, setPreviousTime] = useState(new Date(props.entry.timestamp).getTime());
let [elapsedTime, setElapsedTime] = useState(0);
let isRunning = useRef(true);
useEffect(() => {
const timerInterval = setInterval(startTimer, 1000);
return () => clearInterval(timerInterval);
}, []);
function startTimer() {
console.log('previousTime', previousTime);
console.log('elapsedTime', elapsedTime);
if (isRunning.current) {
const now = Date.now();
setElapsedTime(elapsedTime + (now - previousTime));
setPreviousTime(now);
}
}
function handlePause() {
isRunning.current = isRunning.current ? false : true
console.log('isRunning', isRunning.current);
if (!isRunning.current) {
setPreviousTime(Date.now());
console.log(previousTime);
}
}
let hours = 0, minutes = 0, seconds = 0;
let secondsDiffence, secondsText, minutesText, hoursText;
secondsDiffence = Math.floor(elapsedTime / 1000);
hours = Math.floor(secondsDiffence / 3600);
minutes = Math.floor((secondsDiffence - (hours * 3600)) / 60);
seconds = Math.floor(secondsDiffence - (hours * 3600) - (minutes * 60));
secondsText = seconds < 10 ? `0${seconds}` : `${seconds}`;
minutesText = minutes < 10 ? `0${minutes}` : `${minutes}`;
hoursText = hours < 10 ? `0${hours}` : `${hours}`;
console.log('elapsed outside -->', elapsedTime);
return (
<div>
<div id="timer">
{`${hoursText}:${minutesText}:${secondsText}`}
</div>
<button
id="pausePlay"
className="mainButton"
onClick={handlePause}>
{isRunning.current ? 'Pause' : 'Continue'}
</button>
<Link to="/" id="stopSave" className="mainButton" onClick={() => {
props.updateEntry(props.newEntry, elapsedTime, "elapsedTime");
props.submitEntry();
}}>Stop & Save</Link>
</div>
);
}
export default Timer;
以前的工人阶级组成部分如下:
import React from 'react';
import { Link } from 'react-router-dom';
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {
isRunning: true,
previousTime: new Date(this.props.entry.timestamp).getTime(),
elapsedTime: 0
};
this.startTimer = this.startTimer.bind(this);
this.handlePause = this.handlePause.bind(this);
}
componentDidMount() {
this.timerInstance = setInterval(() => this.startTimer(), 1000);
}
componentWillUnmount() {
clearInterval(this.timerInstance);
}
startTimer() {
if (this.state.isRunning) {
const now = Date.now();
this.setState(prevState => ({
previousTime: now,
elapsedTime: prevState.elapsedTime + (now - prevState.previousTime)
}));
}
}
handlePause() {
this.setState(prevState => ({
isRunning: !prevState.isRunning
}));
if (!this.state.isRunning) {
this.setState({
previousTime: Date.now()
});
}
}
render() {
let hours = 0, minutes = 0, seconds = 0;
let secondsDiffence, secondsText, minutesText, hoursText;
secondsDiffence = Math.floor(this.state.elapsedTime / 1000);
hours = Math.floor(secondsDiffence / 3600);
minutes = Math.floor((secondsDiffence - (hours * 3600)) / 60);
seconds = Math.floor(secondsDiffence - (hours * 3600) - (minutes * 60));
secondsText = seconds < 10 ? `0${seconds}` : `${seconds}`;
minutesText = minutes < 10 ? `0${minutes}` : `${minutes}`;
hoursText = hours < 10 ? `0${hours}` : `${hours}`;
return (
<div>
<div id="timer">{`${hoursText}:${minutesText}:${secondsText}`}</div>
<button id="pausePlay" className="mainButton" onClick={this.handlePause}>{this.state.isRunning ? 'Pause' : 'Continue'}</button>
<Link to="/" id="stopSave" className="mainButton" onClick={() => {
props.updateEntry(props.newEntry, this.state.elapsedTime, "elapsedTime");
props.submitEntry();
}}>Stop & Save</Link>
</div>
);
}
}
export default Timer;
啊,这是从类组件更改为基于钩子的组件时的经典问题。您需要取消学习从类组件中了解的内容,以便理解发生了什么。(要阅读更多原因,我建议您阅读这篇文章,它真的很有帮助:https://overreacted.io/a-complete-guide-to-useeffect/(
所以在基于钩子的组件中,每次渲染发生时,React 都会调用你的组件函数来了解 DOM 树。所以基本上,每次调用函数时,都有一个单独的调用堆栈,函数内部的每个状态都会有不同的值,因此,setInterval
/setTimer
的回调在创建时将具有对该值的闭包访问权限(每次调用函数时,都会有一个单独的startTimer
回调(。
这就是useEffect
钩发挥作用的地方。您需要告诉useEffect
钩子"更新"回调以访问状态的最新"版本"。简而言之,这是我的建议:
import React, { useState, useEffect, useRef } from 'react';
import { Link } from 'react-router-dom';
const Timer = (props) => {
let [previousTime, setPreviousTime] = useState(new Date(props.entry.timestamp).getTime());
let [elapsedTime, setElapsedTime] = useState(0);
let isRunning = useRef(true);
useEffect(() => {
function startTimer() {
console.log('previousTime', previousTime);
console.log('elapsedTime', elapsedTime);
if (isRunning.current) {
const now = Date.now();
setElapsedTime(elapsedTime + (now - previousTime));
setPreviousTime(now);
}
}
const timerInterval = setInterval(startTimer, 1000);
return () => clearInterval(timerInterval);
}, [previousTime, elapsedTime]); // The callback depends on those state, you need to put them here
function handlePause() {
isRunning.current = isRunning.current ? false : true
console.log('isRunning', isRunning.current);
if (!isRunning.current) {
setPreviousTime(Date.now());
console.log(previousTime);
}
}
let hours = 0, minutes = 0, seconds = 0;
let secondsDiffence, secondsText, minutesText, hoursText;
secondsDiffence = Math.floor(elapsedTime / 1000);
hours = Math.floor(secondsDiffence / 3600);
minutes = Math.floor((secondsDiffence - (hours * 3600)) / 60);
seconds = Math.floor(secondsDiffence - (hours * 3600) - (minutes * 60));
secondsText = seconds < 10 ? `0${seconds}` : `${seconds}`;
minutesText = minutes < 10 ? `0${minutes}` : `${minutes}`;
hoursText = hours < 10 ? `0${hours}` : `${hours}`;
console.log('elapsed outside -->', elapsedTime);
return (
<div>
<div id="timer">
{`${hoursText}:${minutesText}:${secondsText}`}
</div>
<button
id="pausePlay"
className="mainButton"
onClick={handlePause}>
{isRunning.current ? 'Pause' : 'Continue'}
</button>
<Link to="/" id="stopSave" className="mainButton" onClick={() => {
props.updateEntry(props.newEntry, elapsedTime, "elapsedTime");
props.submitEntry();
}}>Stop & Save</Link>
</div>
);
}
export default Timer;
以下是您可能想要结帐的其他一些链接:
https://overreacted.io/making-setinterval-declarative-with-react-hooks/
https://reactjs.org/docs/hooks-reference.html