我仅使用 React & State 制作计时器,会话时间设置为 2 分钟,休息时间设置为 1 分钟。当点击播放按钮时,计时器将开始递减数值,当会话时间变为0时,它将移至休息时间,当休息时间变为0时,它将移至会话时间,直到按下停止按钮或重置。
这里我面临两个问题:
请从CodePen
找到我的完整代码 handleTimer = () => {
this.interval = setInterval(()=>{
if(this.state.sessionMin == 0 && this.state.Sec == 0){
clearInterval(this.interval);
this.setState({
sessionMin: this.state.sessionConst
})
this.breakInterval = setInterval(()=>{
if(this.state.breakMin == 0 && this.state.Sec == 0){
clearInterval(this.breakInterval);
this.setState({
breakMin: this.state.breakConst
})
console.log(this.state.breakMin)
this.handleTimer()
}
this.setState({
display: 'Break',
Sec: this.state.Sec == 0 ? 10 : this.state.Sec-1,
breakMin: this.state.Sec == 0 ? this.state.breakMin-1 : this.state.breakMin
})
},1000)
}
this.setState({
display: 'Session',
Sec: this.state.Sec == 0 ? 10 : this.state.Sec-1,
sessionMin: this.state.Sec == 0 ? this.state.sessionMin-1 : this.state.sessionMin
})
},1000)
}
注意:第二次设置为 10 秒,而不是 60 秒以进行快速测试。 注意:重置按钮尚未起作用。 注意:当停止按钮后按下播放按钮时,应该从该状态继续,而不是重新开始。
我使用“状态”来获取最新或最近的数据,但它不起作用。我不想尝试钩子或任何其他东西,只想使用简单的组件级状态。
如果您想跳过解释并转到解决方案,请向下滚动。
假设您的会话计时器达到状态会话:0分0秒。看下面的代码(带注释):
// current state: *Session: 0min 0sec*
this.interval = setInterval(()=>{
if (this.state.sessionMin == 0 && this.state.Sec == 0) {
// You tell a browser API "Stop calling this interval function in
// future", but execution of current function call continues !!!
clearInterval(this.interval)
// Execution continues, right? So, this.setState.sessionMin
// takes value of this.state.sessionConst, that is 2
this.setState({ sessionMin: this.state.sessionConst })
// Here you are setting breakInterval, but runtime doesn't care
// about it for now, because first call of this.breakInterval
// will be made after 1000ms
// (setInterval(() => {}, 1000)
// ...wait 1000ms ...
// first call
// ... wait 1000ns
// second call
// ... etc
this.breakInterval = setInterval(()=>{
// ...breakInterval logic
},1000)
}
this.setState({
display: 'Session',
// this.state.Sec == 0, so it's value is set to 10
Sec: this.state.Sec == 0 ? 10 : this.state.Sec-1,
// As mentioned above, this.state.sessionMin is 2,
// so here this.state.sessionMin is set to 1 (2 - 1)
sessionMin: this.state.Sec == 0 ? this.state.sessionMin-1 : this.state.sessionMin
})
},1000)
因此,执行上述代码后,计时器的状态是 Session: 1min 10sec,而不是您预期的 Break: 0min 10sec。 然后1s后breakInterval被调用,看下面的代码:
// current state: *Session: 1min 10sec*
this.breakInterval = setInterval(()=>{
// No, condition is not satisfied, because current
// state is *Session: 1min 10sec*
if(this.state.breakMin == 0 && this.state.Sec == 0){
// ...clear breakInterval logic
}
this.setState({
// this.state.display will be 'Break'
display: 'Break',
// and this.state.Sec will be 9
Sec: this.state.Sec == 0 ? 10 : this.state.Sec-1,
// As mentioned above, current state is still **Session: 1min
// 10sec**, i.e. this.state.Sec is 10. So in
// ternary operator SECOND expression will be executed
// breakMin: this.state.breakMin
breakMin: this.state.Sec == 0 ? this.state.breakMin-1 : this.state.breakMin
})
},1000)
breakMin 在构造函数中初始化为 1
this.state={
display: 'Session',
sessionMin: 2,
sessionConst:2,
// Yep, here
breakMin: 1,
breakConst: 1,
Sec: 0,
}
瞧!在会话:1分10秒之后,您的计时器状态现在为休息:1分9秒。这里有几个问题:
为什么在第一个周期计时器状态Session:1min 10sec更改为Break:0min 9sec,即分钟数是正确的?坦白说,不正确。 因此,在 Break: 1min 9sec 计时器达到Break: 0min 0sec 后。再次看下面的代码:
// current state: Break: 0min 0sec**
this.breakInterval = setInterval(()=>{
// Yes, condition is satisfied
if(this.state.breakMin == 0 && this.state.Sec == 0){
// You tell a browser API "Stop calling this function in
// future", but current function execution continues !!!
clearInterval(this.breakInterval)
// this.state.breakMin is now 1
this.setState({ breakMin: this.state.breakConst })
console.log(this.state.breakMin)
// this.handleTimer() is called, but this.interval()
// will be called after 1000ms
this.handleTimer()
}
this.setState({
// this.state.display will be 'Break'
display: 'Break',
// this.state.Sec will be 10
Sec: this.state.Sec == 0 ? 10 : this.state.Sec-1,
// this.state.Sec is still 0, so in ternary operator
// FIRST expression will be executed
// breakMin: this.state.breakMin - 1
// Because current value of this.state.breakMin is 1,
// result value will be 0 (1 - 1) !!!!!!!
breakMin: this.state.Sec == 0 ? this.state.breakMin-1 : this.state.breakMin
})
},1000)
执行上述代码后,this.state.breakMin的实际值将是0,而不是1,当前状态将是Break:0min 10sec。之后,将调用会话间隔,状态将是Session:1min 9sec,循环将再次开始。 假设会话间隔达到Session: 0min 0sec。您还记得,之后计时器状态将为Session:1min 10sec(请参阅第一个代码示例)。所以:
// **Session: 1min 10sec**
this.breakInterval = setInterval(()=>{
// No, condition is not satisfied
if(this.state.breakMin == 0 && this.state.Sec == 0){
// ...clear breakInterval logic
}
this.setState({
display: 'Break',
// this.state.Sec will be 9
Sec: this.state.Sec == 0 ? 10 : this.state.Sec-1,
// this.state.Sec is 10, so in
// ternary operator SECOND expression will be executed
// breakMin: this.state.breakMin
// Beacause actual value of this.state.breakMin is 0,
// new breakMin value will be zero too
breakMin: this.state.Sec == 0 ? this.state.breakMin-1 : this.state.breakMin
})
},1000)
因此,在第一个周期之后,分钟数是正确的(尽管内部的所有内容都不能正常工作)
所以,解决方案:
class TypesOfFood extends React.Component {
constructor(props) {
super(props)
this.state={
display: 'Session',
paused: false,
sec: 0,
min: this.sessionMinutesConst,
}
}
// use 9, not 10 for tests or 59, not 60 in "production"
// it will be look like:
// 2:01 -> 2:00 -> 1:59
secondsInOneMinute = 9
sessionMinutesConst = 2
breakMinutesConst = 1
timer = null
decrementTimer = () => {
this.setState(currentState => ({
sec: currentState.sec === 0 ? this.secondsInOneMinute : currentState.sec - 1,
min: currentState.sec === 0 ? currentState.min - 1 : currentState.min
}))
}
createTimer = () => {
this.timer = setInterval(() => {
this.handleTimer()
this.decrementTimer()
}, 1000)
}
handleTimer = () => {
const { sec, min } = this.state
const isSessionTimer = this.state.display === 'Session'
if (sec === 0 && min === 0) {
this.setState({
display: isSessionTimer ? 'Break' : 'Session',
min: isSessionTimer ? this.breakMinutesConst : this.sessionMinutesConst
})
}
}
handleStop = () => {
this.setState({ paused: true })
clearInterval(this.timer)
}
handleReset = () => {
clearInterval(this.timer)
this.timer = null
this.setState({
display: 'Session',
paused: false,
sec: 0,
min: this.sessionMinutesConst,
})
}
handlePlay = () => {
if (this.timer && !this.state.paused) return
this.createTimer()
this.setState({ paused: false })
}
render() {
return (
<div id="container">
<h3>{this.state.display}</h3>
<h1>{this.state.min}m:{this.state.sec}s</h1>
<button onClick={this.handlePlay}>Play</button>
<button onClick={this.handleStop}>Stop</button>
<button onClick={this.handleReset}>Reset</button>
</div>
)
}
}
需要注意的事项:
希望有帮助!