父组件使用 CountDown组件
import { CountDown } from '@/components'
// 不写默认值是 60秒
const lastTime = 60
function Page() {
const onFinish = () => {
console.log('end')
}
return (
<div>
<CountDown onEnd={onFinish} value={lastTime} />
</div>
)
}
export default Page
CountDown
import { useState, useEffect } from 'react';
interface IProps {
time: number;
onEnd: () => void;
}
const CountDown = (props: IProps) => {
const { time, onEnd } = props;
const [count, setCount] = useState(time || 60);
useEffect(() => {
const timer = setInterval(() => {
setCount((count) => {
if (count === 0) {
clearInterval(timer);
onEnd && onEnd();
return count;
}
return count - 1;
});
}, 1000);
return () => {
clearInterval(timer);
};
}, [time, onEnd]);
return <div>{count}</div>;
};
export default CountDown;
RAF.setTimeout
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import RAF from '@utils/requestAnimationFrame'
/**
* @description 60秒倒计时
* react组件生命周期:
* constructor -> componentWillMount -> render -> componentDidMount
*/
class CountDown extends PureComponent {
timer = null
interval = 1000
state = {
second: this.props.value
}
static propTypes = {
value: PropTypes.number,
onChange: PropTypes.func.isRequired
}
static defaultProps = {
value: 60,
onChange: () => { }
}
componentDidMount() {
if (this.timer) RAF.clearTimeout(this.timer)
this.tick()
}
componentWillUnmount() {
if (this.timer) RAF.clearTimeout(this.timer)
}
tick = () => {
let { second } = this.state
const { value, onChange } = this.props
this.timer = RAF.setTimeout(() => {
if (second <= 1) {
onChange()
this.setState({ second: value }, () => this.tick())
return
}
second -= 1
this.setState({ second }, () => this.tick())
}, 1000)
}
render() {
const { second } = this.state
const text = `${second}`.padStart(2, '0')
return <span>{text}</span>
}
}
export default CountDown
requestAnimationFrame
requestAnimationFrame实现 setTimeout,setInterval
/**
* @description requestAnimationFrame 实现 setInterval,解决内存溢出
*
* 页面实时的请求接口,控制组件的位置。当大量组件使用了计时器,会造成网页内存溢出
*/
const [initCallback, initInterval] = [() => { }, 1000]
const requestAnimationFrame = window.requestAnimationFrame
const RAF = {
intervalTimer: null,
timeoutTimer: null,
// 实现 setTimeout 倒计时
setTimeout(callback = initCallback, interval = initInterval) {
const startTime = Date.now()
const loop = () => {
this.timeoutTimer = requestAnimationFrame(loop)
const endTime = Date.now()
if (endTime - startTime >= interval) {
// 一定要先清除,后调用 callback,不然死循环
this.clearTimeout()
callback()
}
}
this.timeoutTimer = requestAnimationFrame(loop)
return this.timeoutTimer
},
// 想重复不断的执行 requestAnimationFrame 实现 setInterval
setInterval(callback = initCallback, interval = initInterval) {
let startTime = Date.now()
const loop = () => {
this.intervalTimer = requestAnimationFrame(loop)
const endTime = Date.now()
if (endTime - startTime >= interval) {
startTime = endTime
callback()
}
}
this.intervalTimer = requestAnimationFrame(loop)
return this.intervalTimer
},
// 不要使用箭头函数,this指向 undefined
clearTimeout() { cancelAnimationFrame(this.timeoutTimer) },
clearInterval() { cancelAnimationFrame(this.intervalTimer) },
}
export default RAF
class封装 RAF
/**
* @description requestAnimationFrame 实现 setInterval,解决内存溢出
*
* 页面实时的请求接口,控制组件的位置。当大量组件使用了计时器,会造成网页内存溢出
*/
const [initCallback, initInterval] = [() => { }, 1000]
const requestAnimationFrame = window.requestAnimationFrame
class RAF {
constructor() {
this.intervalTimer = null
this.timeoutTimer = null
this.currentTime = null
}
setInterval(callback = initCallback, interval = initInterval) {
this.currentTime = Date.now()
const loop = () => {
this.intervalTimer = requestAnimationFrame(loop)
const endTime = Date.now()
if (endTime - this.currentTime >= interval) {
this.currentTime = endTime
callback()
}
}
this.intervalTimer = requestAnimationFrame(loop)
return this.intervalTimer
}
setTimeout(callback = initCallback, interval = initInterval) {
this.currentTime = Date.now()
const loop = () => {
this.timeoutTimer = requestAnimationFrame(loop)
const endTime = Date.now()
if (endTime - this.currentTime >= interval) {
// 一定要先清除,后调用 callback,不然死循环
this.clearTimeout()
callback()
}
}
this.timeoutTimer = requestAnimationFrame(loop)
return this.timeoutTimer
}
clearTimeout() {
return cancelAnimationFrame(this.timeoutTimer)
}
clearInterval() {
return cancelAnimationFrame(this.intervalTimer)
}
}
export default RAF
setTimeout
setTimeout实现倒计时
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
/**
* @description 60秒倒计时
* react组件生命周期:
* constructor -> componentWillMount -> render -> componentDidMount
*/
class CountDown extends PureComponent {
timer = null
interval = 1000
state = {
second: this.props.value
}
static propTypes = {
value: PropTypes.number,
onChange: PropTypes.func.isRequired
}
static defaultProps = {
value: 60,
onChange: () => {}
}
componentDidMount() {
if (this.timer) clearTimeout(this.timer)
this.tick()
}
componentWillUnmount() {
if (this.timer) clearTimeout(this.timer)
}
tick = () => {
let { second } = this.state
const { value, onChange } = this.props
this.timer = setTimeout(() => {
if (second <= 0) {
onChange()
this.setState({ second: value }, () => this.tick())
return
}
second -= 1
this.setState({ second }, () => this.tick())
}, this.interval)
}
render() {
const { second } = this.state
return <span>{second}</span>
}
}
export default CountDown