SetTimeout运行两次,仅当重构组件时才运行



所以这很奇怪。首先,这里是我负责超时的部分代码:

constructor(props) {
super(props);
this.state = {
assignments: [],
answers: [],
uploadedFile: '',
showTimeoutModal: false,
};
this.onImageDrop = this.onImageDrop.bind(this);
this.startCountDown = this.startCountDown.bind(this);
this.handleTimeoutButtonClick = this.handleTimeoutButtonClick.bind(this);
}

componentDidMount() {
clearTimeout(this.state.timer);
firebase.database().ref('Works').child(this.props.assignmentId).once('value', () => {
// var startTime = r.val().acceptedDate.time;
var startTime = Time.generate().time; //todo change to db specific time
var waitingTime = Time.makeMinutes(5);
var endTime = Time.generate().time + waitingTime;
this.setState({timer: this.startCountDown(startTime, endTime)});
}).catch(e => console.log(e));
}
componentWillUnmount() {
clearTimeout(this.state.timer);
}
startCountDown(startTime, endTime) {
var timeout = setTimeout(function () {
var now = startTime;
console.log(endTime - now);
if (now >= endTime) {
clearTimeout(timeout);
console.log(true);
this.setState({showTimeoutModal: true});
} else {
now = Time.generate().time;
this.setState({timer: this.startCountDown(now, endTime)});
}
}.bind(this), 1000);
return timeout;
}
handleTimeoutButtonClick() {
var startTime = Time.generate().time;
var waitingTime = Time.makeMinutes(0.15);
var endTime = Time.generate().time + waitingTime;
this.setState({showTimeoutModal: false, timer: this.startCountDown(startTime, endTime)});
}

我的项目的工作方式是:用户选择要解决的任务。我还不完全理解React,所以我不完全知道后台发生了什么。但事实是,当我第一次构建组件时,一切都很顺利。当我将组件留给另一条路线时,一切都会正常工作。但如果我重新创建这个组件,它就会全部崩溃。

以下是完整的组件:

import React from 'react';
import firebase from '../../firebase';
import {Col, Button, Modal} from 'react-bootstrap';
import Dropzone from 'react-dropzone';
import CancelAnswerButton from '../answer/CancelAnswerButton';
import FinishAnswerButton from '../answer/FinishAnswerButton';
import AnswerComponent from '../answer/AnswerComponent';
import MetaTags from 'react-meta-tags';
import {Redirect} from 'react-router';
import './AssignmentComponent.css';
import 'font-awesome/css/font-awesome.min.css';
import Time from '../globalMethods/Time';
export default class AssignmentComponent extends React.Component {
async componentWillMount() {
firebase.database().ref('Users').child(firebase.auth().currentUser.uid).on('value', snap => {
if (snap.val().assignedWork) {
this.setState({hasAssignedTask: true});
} else {
this.setState({hasAssignedTask: false});
}
});
const assignmentsRef = firebase.database().ref('Works').orderByChild('firebaseKey').equalTo(this.props.assignmentId);
await assignmentsRef.on('value', snapshot => {
var assignments = snapshot.val();
var newState = [];
for (let assignment in assignments) {
let currentAssignment = assignments[assignment];
newState.push({
id: assignment,
category: currentAssignment.category,
level: currentAssignment.level,
pointAmount: currentAssignment.pointAmount,
pointBoost: currentAssignment.pointBoost,
points: currentAssignment.pointBoost + currentAssignment.pointAmount,
photoURL: currentAssignment.photoURL,
workText: currentAssignment.workText,
answers: currentAssignment.answers,
});
}
var answers = [];
for (let answer in newState[0].answers) {
let currAns = newState[0].answers[answer];
answers.push({
id: answer,
textAnswer: currAns.textAnswer,
downloadURL: currAns.downloadURL,
})
}
this.setState({
assignments: newState,
answers: answers,
});
});
}
constructor(props) {
super(props);
this.state = {
assignments: [],
answers: [],
uploadedFile: '',
showTimeoutModal: false,
};
this.onImageDrop = this.onImageDrop.bind(this);
this.startCountDown = this.startCountDown.bind(this);
this.handleTimeoutButtonClick = this.handleTimeoutButtonClick.bind(this);
}
componentDidMount() {
clearTimeout(this.state.timer);
firebase.database().ref('Works').child(this.props.assignmentId).once('value', function () {
// var startTime = r.val().acceptedDate.time;
var startTime = Time.generate().time; //todo change to db specific time
var waitingTime = Time.makeMinutes(5);
var endTime = Time.generate().time + waitingTime;
this.setState({timer: this.startCountDown(startTime, endTime)});
}.bind(this)).catch(e => console.log(e));
}
componentWillUnmount() {
clearTimeout(this.state.timer);
}
startCountDown(startTime, endTime) {
var timeout = setTimeout(function () {
var now = startTime;
console.log(endTime - now);
if (now >= endTime) {
clearTimeout(timeout);
console.log(true);
this.setState({showTimeoutModal: true});
} else {
now = Time.generate().time;
this.setState({timer: this.startCountDown(now, endTime)});
}
}.bind(this), 1000);
return timeout;
}
handleTimeoutButtonClick() {
var startTime = Time.generate().time;
var waitingTime = Time.makeMinutes(0.15);
var endTime = Time.generate().time + waitingTime;
this.setState({showTimeoutModal: false, timer: this.startCountDown(startTime, endTime)});
}
async onImageDrop(files) {
await this.setState({
uploadedFile: files[0],
});
await AnswerComponent.handleImageSubmit(files[0], this.props);
}
render() {
return (
this.state.assignments.map(assignment => {
return (
this.state.hasAssignedTask ?
<section key={assignment.id} className='display-assignment'>
<MetaTags>
<title>Zadanie</title>
</MetaTags>
<div style={{height: '150px', background: '#fff', borderBottom: '2px solid #e0e0e0'}}>
<div className='container'>
<h4 style={{
paddingTop: '75px',
fontSize: '24px',
textAlign: 'left'
}}>Aktualnie rozwiązywane zadanie</h4>
</div>
</div>
<div className='wrapper show-grid task_info container-fluid task_solve_content'>
<Col xs={12} md={6}>
<img className='task_img' alt='' src={assignment.photoURL}/>
<p>{assignment.workText}</p>
</Col>
<Col xs={12} md={6}>
<div className='row task_info_content'>
<div className='col-sm-4 col-md-4 col-lg-4'>
<h3 className='text-center gradient_text'>
{assignment.category}
</h3>
<h4 className='text-center'>przedmiot</h4>
</div>
<div className='col-sm-4 col-md-4 col-lg-4'>
<h3 className='text-center gradient_text'>
{assignment.level}</h3>
<h4 className='text-center'>poziom</h4>
</div>
<div className='col-sm-4 col-md-4 col-lg-4'>
<h3 className='text-center gradient_text'>
{assignment.points}</h3>
<h4>punkty</h4>
</div>
</div>
<form encType="multipart/form-data">
<textarea placeholder='Wpisz rozwiązanie...' name="textAnswer" id="textAnswer"
style={{
width: '100%',
height: '80vh',
background: 'white',
color: 'black',
}}/>
<div style={{width: '100%', height: '60px', position: 'relative'}}>
<Button className="send_text_answer_button"
onClick={() => AnswerComponent.handleTextSubmit(this.props)}
style={{display: 'block'}}>
<span>Wyslij odpowiedź tekstową</span>
</Button>
</div>
<Dropzone
className='dropzone'
multiple={false}
accept='image/*'
style={{
backgroundColor: '#fff',
border: '1px solid #fff',
borderBottom: '2px solid #e0e0e0',
borderRadius: '4px',
width: '100%',
height: '300px',
marginBottom: '20px'
}}
onDrop={this.onImageDrop.bind(this)}>
<i className='fa fa-image' style={{
fontSize: '64px',
marginRight: '5px',
color: '#e0e0e0',
margin: 'auto',
marginTop: '120px'
}}/>
</Dropzone>
<h3 style={{display: 'none', color: 'red'}}
id='error'>Upewnij sie ze dodana jest przynajmniej jedna odpowiedz!</h3>
<div id='answers'>
<h3 style={{
fontSize: '18px',
fontWeight: 'lighter',
marginTop: '10px',
marginBottom: '10px'
}}>Odpowiedzi przeslane do tej pory</h3>
{
this.state.answers.map(answer => {
return (
<Col xs={12} md={12} key={answer.id}>
{
answer.textAnswer !== undefined &&
answer.textAnswer.length > 0 ?
<div className='task_info_text_answer'>
  <p>{answer.textAnswer}</p>
</div>
:
<img className="your_answer_img"
   src={answer.downloadURL}
   alt=""/>
}
<br/>
<Button className="delete_answer_button"
onClick={() => AnswerComponent.removeAnswer(this.props, answer.id)}>
<span>Usuń odpowiedź</span>
</Button>
</Col>
)
})
}
</div>
<div className="row" style={{marginTop: '50px', marginBottom: '100px'}}>
<div className="col-sm-6 col-md-6 col-lg-6 cancel_answer_button_content"
style={{height: '200px'}}>
<CancelAnswerButton className="send_text_cancel_answer_button"
points={assignment.pointAmount + assignment.pointBoost}
assignmentId={assignment.id}/>
<div className="cancel_answer_button_info">
<p>Za anulowanie zadania grozi odjęcie punktów z rangi!</p>
</div>
</div>
<div className="col-sm-6 col-md-6 col-lg-6 finish_answer_button_content"
style={{height: '200px'}}>
<FinishAnswerButton className="send_text_finish_button"
points={assignment.pointAmount + assignment.pointBoost}
assignmentId={assignment.id}/>
<div className="finish_answer_button_info">
<p>Upewnij się, że uczeń dostał wszystko, czego potrzebuje!</p>
</div>
</div>
</div>
</form>
</Col>
</div>
<Modal show={this.state.showTimeoutModal}>
<Modal.Header>
Hej! Rozwiazujesz dalej to zadanie?
</Modal.Header>
<Modal.Body>
<h3>Pozostaly czas: {this.state.timeLeft / 1000}</h3>
<Button className='btn btn-primary' onClick={this.handleTimeoutButtonClick}>Tak! Daj
mi jeszcze 5 minut! </Button>
</Modal.Body>
</Modal>
</section>
:
<Redirect to='/assignments'/>
)
})
)
}
}

那么,我怎样才能让计时器只运行一次呢?我错过了什么?

第一次点击后登录控制台:

300000 AssignmentComponent.js:89
298999 AssignmentComponent.js:89
297997 AssignmentComponent.js:89
296996 AssignmentComponent.js:89
295994 AssignmentComponent.js:89
294992 AssignmentComponent.js:89
293990 AssignmentComponent.js:89
292988 AssignmentComponent.js:89
291986 AssignmentComponent.js:89
290984 AssignmentComponent.js:89
289983 AssignmentComponent.js:89

第二次点击后的日志:

300000 AssignmentComponent.js:89
298993 AssignmentComponent.js:89
298999 AssignmentComponent.js:89
297992 AssignmentComponent.js:89
297997 AssignmentComponent.js:89
296990 AssignmentComponent.js:89
296996 AssignmentComponent.js:89
295981 AssignmentComponent.js:89
295993 AssignmentComponent.js:89
294980 AssignmentComponent.js:89
294991 AssignmentComponent.js:89

而且我刚刚意识到,它不仅仅运行两次。它为每次点击启动一个新的计时器。因此,如果重新创建组件5次,将有5个计时器并排运行。

以下是我为应用程序所做的操作。在构造函数中添加一个计时器变量,将setTimeout函数分配给要启动计时器的任何计时器变量。并使用该变量清除超时。

class TimerComponent extends Component {
constructor() {
super();
this.timer = null; //Timer initialisation
}
startTimeout = () => {
this.timer = setTimeout(() => {
}, 500)
}
componentDidMount() {
this.startTimeout();
}
componentWillUnmount() {
clearTimeout(this.timer); //Clearing timeout
this.timer = null;
} 
}

最新更新