写一个关于RN的分秒毫秒组件(组件状态由同一个父组件控制)

介绍一下,就一个界面会一直跑时间,项目有个需求需要用到毫秒级计时器,那我肯定想到用组件了塞,但是组件的状态和组件的数据都是不互通的都是独立的,因此我写了下面这个组件,组件的状态会由父组件控制切记,必须是同一个父组件,因为状态是父组件控制的,如果是多个页面多个父组件的话可以不用看,一键控制所有的组件的开启或者暂停,也可以控制所有组件是否重新启动重新计时,当前也考虑到了如果组件不是同时出现这个数据怎么保持一致的这个方案,组件代码如下

js 复制代码
import React, { useState, useEffect, useRef } from 'react'
import { Text, View, Button, StyleSheet } from 'react-native'

const Timer = ({ isRunning, elapsedTime, onStart, onStop, firstElapsedTime }) => {
  const startTimeRef = useRef(null)
  const animationIdRef = useRef(null)
  const previousTimeRef = useRef(0)

  const startTimer = () => {
    startTimeRef.current = Date.now() - elapsedTime
    animationIdRef.current = requestAnimationFrame(updateTimer)
  }

  const stopTimer = () => {
    cancelAnimationFrame(animationIdRef.current)
  }

  const updateTimer = () => {
    const currentTime = Date.now()
    const elapsedTime = currentTime - startTimeRef.current
    previousTimeRef.current = elapsedTime
    onStart(elapsedTime)
    animationIdRef.current = requestAnimationFrame(updateTimer)
  }

  useEffect(() => {
    if (isRunning) {
      startTimer()
    } else {
      stopTimer()
    }

    return () => {
      stopTimer()
    }
  }, [isRunning])

  const handleControl = () => {
    if (isRunning) {
      onStop()
    } else {
      startTimer()
    }
  }

  const formatTime = (milliseconds) => {
    const minutes = Math.floor(milliseconds / (60 * 1000))
    const seconds = Math.floor((milliseconds % (60 * 1000)) / 1000)
    const millisecondsFraction = Math.floor((milliseconds % 1000) / 10)
    return (
      (minutes < 10 ? '0' : '') + minutes + ':' +
      (seconds < 10 ? '0' : '') + seconds + ':' +
      (millisecondsFraction < 10 ? '0' : '') + millisecondsFraction
    )
  }

  return (
    <View>
      <Text>{formatTime(firstElapsedTime)}</Text>
    </View>
  )
}

const ParentComponent = () => {
  const [timers, setTimers] = useState([
    { id: 1, isRunning: false, elapsedTime: 0 },
    { id: 2, isRunning: false, elapsedTime: 0 },
    { id: 3, isRunning: false, elapsedTime: 0 },
  ])
  const [nextId, setNextId] = useState(4)

  const handleStartAll = () => {
    setTimers(timers.map(timer => ({ ...timer, isRunning: true })))
  }

  const handlePauseAll = () => {
    setTimers(timers.map(timer => ({ ...timer, isRunning: false })))
  }

  const handleContinueAll = () => {
    setTimers(timers.map(timer => ({ ...timer, isRunning: true })))
  }

  const handleRestartAll = () => {
    handlePauseAll()
    setTimeout(() => {
      setTimers(timers.map(timer => ({ ...timer, isRunning: true, elapsedTime: timers[0].elapsedTime })))
    }, 100)
  }

  const handleAddTimer = () => {
    const firstTimerElapsedTime = timers[0].elapsedTime // 获取第一个计时器组件的时间
    const newTimer = { id: nextId, isRunning: false, elapsedTime: firstTimerElapsedTime }
    setNextId(nextId + 1)
    setTimers([...timers, newTimer])
  }

  return (
    <View style={styles.container}>
      <View style={styles.timerContainer}>
        {timers.map(timer => (
          <Timer
            key={timer.id}
            isRunning={timer.isRunning}
            elapsedTime={timer.elapsedTime}
            onStart={elapsedTime => {
              const newTimers = [...timers]
              const index = newTimers.findIndex(t => t.id === timer.id)
              newTimers[index].elapsedTime = elapsedTime
              setTimers(newTimers)
            }}
            onStop={() => {
              const newTimers = [...timers]
              const index = newTimers.findIndex(t => t.id === timer.id)
              newTimers[index].isRunning = false
              setTimers(newTimers)
            }}
            firstElapsedTime={timers[0].elapsedTime} // 传递第一个计时器的elapsedTime给Timer组件
          />
        ))}
      </View>
      <View style={styles.buttonContainer}>
        <Button title={timers.some(timer => timer.isRunning) ? '全部暂停' : '全部开始'} onPress={timers.some(timer => timer.isRunning) ? handlePauseAll : handleStartAll} />
        <Button title="继续" onPress={handleContinueAll} />
        <Button title="重新启动" onPress={handleRestartAll} />
        <Button title="添加新组件(需要先暂停才行,时间最初为00:00:00,点击开始后时间以第一个组件时间为准)" onPress={handleAddTimer} />
      </View>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  timerContainer: {
    marginBottom: 20,
  },
  buttonContainer: {
    flexDirection: 'column',
    justifyContent: 'space-around',
    marginTop: 20,
  },
})

export default ParentComponent

上面的组件说明下:(时间会有差别,但是差别不大,基本在几毫秒之内,为了保持数据的一致性,我将都参照第一个组件的时间为准,点击暂停时,所有组件的时间都跟第一个组件保持一致) 点击开始:所有组件开始计时 点击暂停:所有组件全部暂停 点击重启:计时从0开始,重新计时 点击新增组件:需要先暂停才行,因为页面一直在渲染添加不上,我没解决掉,有大佬能解决了更好,还有就是时间以第一个组件显示的时间为准,包括暂停也都是(为了保证所有组件计时的时间的同步)

PS:上面的那个组件展示没必要用循环奥,直接按照下面写也行【如果需要用循环写,也可用循环】

js 复制代码
<Timer
  key={timers[0].id}
  isRunning={timers[0].isRunning}
  elapsedTime={timers[0].elapsedTime}
  onStart={elapsedTime => {
  const newTimers = [...timers]
  const index = newTimers.findIndex(t => t.id === timers[0].id)
      newTimers[index].elapsedTime = elapsedTime
            setTimers(newTimers)
      }}
   onStop={() => {
        const newTimers = [...timers]
        const index = newTimers.findIndex(t => t.id === timers[0].id)
         newTimers[index].isRunning = false
              setTimers(newTimers)
         }}
   firstElapsedTime={timers[0].elapsedTime} // 传递第一个计时器的elapsedTime给Timer组件
                />

上面代码在需要用到组件的地方直接用就行,相当于每个组件都用第一个的状态,或者每个都用自己的状态其实效果是一样的,这种是对于不需要循环在某一个地方使用的情况,就算是循环直接循环三个这样的也行,总之就是这样写也可以,用循环写也可以,这个各位自己定

相关推荐
打工人小夏几秒前
vue3使用transition组件,实现过度动画
前端·vue.js·前端框架·css3
LFly_ice3 分钟前
Next-1-启动!
开发语言·前端·javascript
小时前端5 分钟前
谁说 AI 历史会话必须存后端?IndexedDB方案完美翻盘
前端·agent·indexeddb
wordbaby9 分钟前
TanStack Router 基于文件的路由
前端
wordbaby14 分钟前
TanStack Router 路由概念
前端
wordbaby16 分钟前
TanStack Router 路由匹配
前端
cc蒲公英17 分钟前
vue nextTick和setTimeout区别
前端·javascript·vue.js
程序员刘禹锡22 分钟前
Html中常用的块标签!!!12.16日
前端·html
我血条子呢32 分钟前
【CSS】类似渐变色弯曲border
前端·css
DanyHope33 分钟前
LeetCode 两数之和:从 O (n²) 到 O (n),空间换时间的经典实践
前端·javascript·算法·leetcode·职场和发展