HOME/Articles/

ReactNative StopWatch作るの結構むずい

Article Outline

'タイトル'

はじめに

ストップウォッチ作るコードってラップ機能付きだとあまりないし, React Hooksってなるとほんとに見つからないよね

コード

const [now, setNow] = useState(0);
const [start, setStart] = useState(0);
const [intervalId, setIntervalId] = useState<number>();
const [laps, setLaps] = useState<number[]>([]);

useEffect(() => {
  // component will unmount
  return () => clearInterval(intervalId);
}, []);

const handleStart = () => {
  const id = setInterval(() => {
    setNow(new Date().getTime());
  }, 100);
  setIntervalId(id);
  setStart(new Date().getTime());
  setLaps([0]);
};

const handleStop = () => {
  clearInterval(intervalId);
  const [firstLap, ...other] = laps;
  setLaps([firstLap + now - start, ...other]);
  setStart(0);
  setNow(0);
};

const handleResume = () => {
  setStart(new Date().getTime());
  setNow(new Date().getTime());
  const id = setInterval(() => {
    setNow(new Date().getTime());
  }, 100);
  setIntervalId(id);
};

const handleLap = () => {
  const [firstLap, ...other] = laps;
  setLaps([0, firstLap + now - start, ...other]);
  setStart(new Date().getTime());
  setNow(new Date().getTime());
};

const handleReset = () => {
  setLaps([]);
  setStart(0);
  setNow(0);
};

解説

必要なstateは

  • now: 今の時刻
  • state: タイマーを開始した時刻
  • laps: ラップを格納する配列
  • intervalId: タイマーのId

handleStart関数内のsetIntervalでタイマーを開始し, 返り値のidをセットしてラップを0にします.

handleStop関数内のclearIntervalでタイマーを停止し, ラップは firstLap + now - start にします.
firstLapはラップの最初の値, now - start が経過時間です.

handleResume関数は 停止 -> 再開 の時の関数です.

handleRap関数はラップ時の関数で, タイマーは動かしたまま, lapに記録します.

handleResetで全てをリセットします.

最後にuseEffectのcleanup関数でタイマーをクリアーします.

表示部分

const Timer = ({ interval, style }: any) => {
  const pad = (n: number) => n < 10 ? "0" + n : n;
  const duration = moment.duration(interval);
  const centimes = Math.floor(duration.milliseconds() / 10);
  return (
    <View style={styles.timerContainer}>
      {duration.minutes() < 0
        ? (
          <React.Fragment>
            <Text style={style}>00</Text>
            <Text style={style}>00</Text>
            <Text style={style}>00</Text>
          </React.Fragment>
        )
        : (
          <React.Fragment>
            <Text style={style}>{pad(duration.minutes())}</Text>
            <Text style={style}>{pad(duration.seconds())}</Text>
            <Text style={style}>{pad(centimes)}</Text>
          </React.Fragment>
        )}
    </View>
  );
};

return (
  <React.Fragment>
    <Timer
      interval={laps.reduce((prev, curr) => prev + curr, 0) + now - start}
      style={styles.timer}
    />
    {laps.length === 0 && (
      <TouchableOpacity onPress={handleStart}>
        <Text>START</Text>
      </TouchableOpacity>
    )}
    {start > 0 && (
      <React.Fragment>
        <TouchableOpacity onPress={handleStop}>
          <Text>STOP</Text>
        </TouchableOpacity>
        <TouchableOpacity onPress={handleLap}>
          <Text>LAP</Text>
        </TouchableOpacity>
      </React.Fragment>
    )}
    {laps.length > 0 && start === 0 && (
      <React.Fragment>
        <TouchableOpacity onPress={handleReset}>
          <Text>RESET</Text>
        </TouchableOpacity>
        <TouchableOpacity onPress={handleResume}>
          <Text>RESUME</Text>
        </TouchableOpacity>
      </React.Fragment>
    )}
    {laps.map((lap, i) => <Text key={i}>{lap}</Text>)}
  </React.Fragment>
);

解説

合計値は laps.reduce((prev, curr) => prev + curr, 0) + now - start で求められます.
あとは大体いい感じにです..