父组件使用 CountDown组件

  1. import { CountDown } from '@/components'
  2. // 不写默认值是 60秒
  3. const lastTime = 60
  4. function Page() {
  5. const onFinish = () => {
  6. console.log('end')
  7. }
  8. return (
  9. <div>
  10. <CountDown onEnd={onFinish} value={lastTime} />
  11. </div>
  12. )
  13. }
  14. export default Page

CountDown

  1. import { useState, useEffect } from 'react';
  2. interface IProps {
  3. time: number;
  4. onEnd: () => void;
  5. }
  6. const CountDown = (props: IProps) => {
  7. const { time, onEnd } = props;
  8. const [count, setCount] = useState(time || 60);
  9. useEffect(() => {
  10. const timer = setInterval(() => {
  11. setCount((count) => {
  12. if (count === 0) {
  13. clearInterval(timer);
  14. onEnd && onEnd();
  15. return count;
  16. }
  17. return count - 1;
  18. });
  19. }, 1000);
  20. return () => {
  21. clearInterval(timer);
  22. };
  23. }, [time, onEnd]);
  24. return <div>{count}</div>;
  25. };
  26. export default CountDown;

RAF.setTimeout

  1. import React, { PureComponent } from 'react'
  2. import PropTypes from 'prop-types'
  3. import RAF from '@utils/requestAnimationFrame'
  4. /**
  5. * @description 60秒倒计时
  6. * react组件生命周期:
  7. * constructor -> componentWillMount -> render -> componentDidMount
  8. */
  9. class CountDown extends PureComponent {
  10. timer = null
  11. interval = 1000
  12. state = {
  13. second: this.props.value
  14. }
  15. static propTypes = {
  16. value: PropTypes.number,
  17. onChange: PropTypes.func.isRequired
  18. }
  19. static defaultProps = {
  20. value: 60,
  21. onChange: () => { }
  22. }
  23. componentDidMount() {
  24. if (this.timer) RAF.clearTimeout(this.timer)
  25. this.tick()
  26. }
  27. componentWillUnmount() {
  28. if (this.timer) RAF.clearTimeout(this.timer)
  29. }
  30. tick = () => {
  31. let { second } = this.state
  32. const { value, onChange } = this.props
  33. this.timer = RAF.setTimeout(() => {
  34. if (second <= 1) {
  35. onChange()
  36. this.setState({ second: value }, () => this.tick())
  37. return
  38. }
  39. second -= 1
  40. this.setState({ second }, () => this.tick())
  41. }, 1000)
  42. }
  43. render() {
  44. const { second } = this.state
  45. const text = `${second}`.padStart(2, '0')
  46. return <span>{text}</span>
  47. }
  48. }
  49. export default CountDown

requestAnimationFrame

requestAnimationFrame实现 setTimeout,setInterval

  1. /**
  2. * @description requestAnimationFrame 实现 setInterval,解决内存溢出
  3. *
  4. * 页面实时的请求接口,控制组件的位置。当大量组件使用了计时器,会造成网页内存溢出
  5. */
  6. const [initCallback, initInterval] = [() => { }, 1000]
  7. const requestAnimationFrame = window.requestAnimationFrame
  8. const RAF = {
  9. intervalTimer: null,
  10. timeoutTimer: null,
  11. // 实现 setTimeout 倒计时
  12. setTimeout(callback = initCallback, interval = initInterval) {
  13. const startTime = Date.now()
  14. const loop = () => {
  15. this.timeoutTimer = requestAnimationFrame(loop)
  16. const endTime = Date.now()
  17. if (endTime - startTime >= interval) {
  18. // 一定要先清除,后调用 callback,不然死循环
  19. this.clearTimeout()
  20. callback()
  21. }
  22. }
  23. this.timeoutTimer = requestAnimationFrame(loop)
  24. return this.timeoutTimer
  25. },
  26. // 想重复不断的执行 requestAnimationFrame 实现 setInterval
  27. setInterval(callback = initCallback, interval = initInterval) {
  28. let startTime = Date.now()
  29. const loop = () => {
  30. this.intervalTimer = requestAnimationFrame(loop)
  31. const endTime = Date.now()
  32. if (endTime - startTime >= interval) {
  33. startTime = endTime
  34. callback()
  35. }
  36. }
  37. this.intervalTimer = requestAnimationFrame(loop)
  38. return this.intervalTimer
  39. },
  40. // 不要使用箭头函数,this指向 undefined
  41. clearTimeout() { cancelAnimationFrame(this.timeoutTimer) },
  42. clearInterval() { cancelAnimationFrame(this.intervalTimer) },
  43. }
  44. export default RAF

class封装 RAF

  1. /**
  2. * @description requestAnimationFrame 实现 setInterval,解决内存溢出
  3. *
  4. * 页面实时的请求接口,控制组件的位置。当大量组件使用了计时器,会造成网页内存溢出
  5. */
  6. const [initCallback, initInterval] = [() => { }, 1000]
  7. const requestAnimationFrame = window.requestAnimationFrame
  8. class RAF {
  9. constructor() {
  10. this.intervalTimer = null
  11. this.timeoutTimer = null
  12. this.currentTime = null
  13. }
  14. setInterval(callback = initCallback, interval = initInterval) {
  15. this.currentTime = Date.now()
  16. const loop = () => {
  17. this.intervalTimer = requestAnimationFrame(loop)
  18. const endTime = Date.now()
  19. if (endTime - this.currentTime >= interval) {
  20. this.currentTime = endTime
  21. callback()
  22. }
  23. }
  24. this.intervalTimer = requestAnimationFrame(loop)
  25. return this.intervalTimer
  26. }
  27. setTimeout(callback = initCallback, interval = initInterval) {
  28. this.currentTime = Date.now()
  29. const loop = () => {
  30. this.timeoutTimer = requestAnimationFrame(loop)
  31. const endTime = Date.now()
  32. if (endTime - this.currentTime >= interval) {
  33. // 一定要先清除,后调用 callback,不然死循环
  34. this.clearTimeout()
  35. callback()
  36. }
  37. }
  38. this.timeoutTimer = requestAnimationFrame(loop)
  39. return this.timeoutTimer
  40. }
  41. clearTimeout() {
  42. return cancelAnimationFrame(this.timeoutTimer)
  43. }
  44. clearInterval() {
  45. return cancelAnimationFrame(this.intervalTimer)
  46. }
  47. }
  48. export default RAF

setTimeout

setTimeout实现倒计时

  1. import React, { PureComponent } from 'react'
  2. import PropTypes from 'prop-types'
  3. /**
  4. * @description 60秒倒计时
  5. * react组件生命周期:
  6. * constructor -> componentWillMount -> render -> componentDidMount
  7. */
  8. class CountDown extends PureComponent {
  9. timer = null
  10. interval = 1000
  11. state = {
  12. second: this.props.value
  13. }
  14. static propTypes = {
  15. value: PropTypes.number,
  16. onChange: PropTypes.func.isRequired
  17. }
  18. static defaultProps = {
  19. value: 60,
  20. onChange: () => {}
  21. }
  22. componentDidMount() {
  23. if (this.timer) clearTimeout(this.timer)
  24. this.tick()
  25. }
  26. componentWillUnmount() {
  27. if (this.timer) clearTimeout(this.timer)
  28. }
  29. tick = () => {
  30. let { second } = this.state
  31. const { value, onChange } = this.props
  32. this.timer = setTimeout(() => {
  33. if (second <= 0) {
  34. onChange()
  35. this.setState({ second: value }, () => this.tick())
  36. return
  37. }
  38. second -= 1
  39. this.setState({ second }, () => this.tick())
  40. }, this.interval)
  41. }
  42. render() {
  43. const { second } = this.state
  44. return <span>{second}</span>
  45. }
  46. }
  47. export default CountDown