LoginSignup
7

More than 1 year has passed since last update.

「いま何時?」「ReactとTypeScriptで時計を作っといたで」

Posted at

というわけで、下記のように現在時刻をアナログ・デジタルの両方で表示する時計を作りました。特段、スゴイことを書いてあることではありませんのご承知おきください。

clock.gif

利用技術

  • emotion/react 11.8.2
  • emotion/styled 11.8.1
  • react 17.0.2
  • typescript 4.6.3
  • dayjs 1.11.0

リンク先について

GitHubでソースを公開しています。issue/プルリク募集しています!
またデモについてはこちらで公開しています。

ソースコードについて

とくに特別なところもないですが、自分なりに気をつけたことを書いておきます。

ディレクトリ構成について

下記構成でディレクトリをつくりました。
最近、みた海外のYouTuberがやっていたので、真似てみました。

src/
 ├ businesses/
 |    └ buildHand.tsx
 ├ components/
 |    ├ AnalogClock.tsx
 |    ├ Clocks.tsx
 |    ├ DigitalClock.tsx
 |    └ Hand.tsx
 ├ hooks/
 |    └ useTime.tsx
 ├ interfaces/
 |    ├ ClockNum.tsx
 |    └ TimeFormat.tsx
 ├ App.tsx
 └ index.tsx
  • businesses:ビジネスロジックを記述するファイルをまとめたディレクトリ
  • components:コンポーネントをまとめたディレクトリ
  • hooks:カスタムフックをまとめたディレクトリ
  • interfaces:インターフェイスをまとめたディレクトリ

Clocks.tsx

時間の状態を管理をするためのカスタムフックを作り、timeという定数にいれます。
timeはデジタル時計とアナログ時計の両方のコンポーネントで使うのでpropsとして渡します。

src/components/Clocks.tsx
import DigitalClock from 'components/DigitalClock'
import AnalogClock from 'components/AnalogClock'
import { useTime } from 'hooks/useTime'

const Clocks: React.VFC = () => {
  const time = useTime(1000)
  return (
    <>
      <AnalogClock time={time} />
      <DigitalClock time={time} />
    </>
  )
}

export default Clocks

useTime.tsx

毎秒、Date関数で取得した現在時刻を取得。stateを更新して返すようにしています。

src/hooks/useTime.tsx
import { useEffect, useState } from 'react'

export const useTime = (interval: number) => {
  const [time, updateTime] = useState(Date.now())

  useEffect(() => {
    const timeoutId = setTimeout(() => updateTime(Date.now()), interval)
    return () => {
      clearTimeout(timeoutId)
    }
  }, [time]) // eslint-disable-line react-hooks/exhaustive-deps

  return time
}

DigitalClock.tsx

propsとして現在時刻を受け取っているので、日付操作ライブラリのday.jsで整形して表示させます。

tsx:src/components/DigitalClock.tsx
import dayjs from 'dayjs'
import { ClockNum } from 'interfaces/ClockNum'
import styled from '@emotion/styled'

const DigitalClock: React.VFC<ClockNum> = ({ time }) => {
  return <SClockText>{dayjs(time).format('HH:mm:ss')}</SClockText>
}

const SClockText = styled.div`
  font-size: 50px;
  color: #ffffff;
  margin-top: 100px;
`

export default DigitalClock

AnalogClock.tsx

アナログ時計ではpropsとして受け取った現在時刻から、時針・分針・秒針を生成する必要があります。
そのために、以下2つを準備しました。

buildHandメソッド
針の種類や角度の情報を含んだ配列を生成するメソッド

Handコンポーネント
buildHandメソッドから返された配列から実際に針を描画するコンポーネント

tsx:src/components/AnalogClock.tsx
import styled from '@emotion/styled'
import { buildHand } from 'businesses/buildHand'
import { ClockNum } from 'interfaces/ClockNum'
import { TimeFormat } from 'interfaces/TimeFormat'
import { useEffect, useState } from 'react'
import Hand from './Hand'

const AnalogClock: React.VFC<ClockNum> = ({ time }) => {
  const [hand, setHand] = useState<TimeFormat[]>([])
  useEffect(() => {
    setHand(buildHand())
  }, [])
  

  return (
    <SClockBoard>
      {hand.map((value, index) => (
        <Hand key={index} time={time} format={value.format} separate={value.separate} />
      ))}
    </SClockBoard>
  )
}

const SClockBoard = styled.div`
  width: 400px;
  height: 400px;
  border: 1px solid #ffffff;
  border-radius: 100%;
  position: relative;
  &:before {
    content: '';
    diplay: block;
    width: 5px;
    height: 5px;
    border-radius: 100%;
    background-color: #ffffff;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
  }
`
export default AnalogClock

buildHand.tsx

formatキーは針の種類です。

  • h:時針
  • m:分針
  • s;秒針

separateキーは針の角度が何分割されるか表します。
1週は360度なので、それぞれ適切な分割数で割ります。

src/businesses/buildHand.tsx
export const buildHand = () => {
  const timeFormatArray = [
    {
      format: 'h',
      separate: 12,
    },
    {
      format: 'm',
      separate: 60,
    },
    {
      format: 's',
      separate: 60,
    },
  ]

  return timeFormatArray.map((value) => {
    return {
      format: value.format,
      separate: 360 / value.separate,
    }
  })
}

Hand.tsx

buildHandメソッドで生成された配列情報から針を描画します。
cssのtransformプロパティで角度を変えるようにしました。

src/components/Hand.tsx
import styled from '@emotion/styled'
import { ClockNum } from 'interfaces/ClockNum'
import { TimeFormat } from 'interfaces/TimeFormat'
import dayjs from 'dayjs'

type Props = ClockNum & TimeFormat

const Hand: React.VFC<Props> = ({ time, format, separate }) => {
  const now: number = Number(dayjs(time).format(format))
  const Angle = now * separate
  let handStyles
  if (format === 'h') {
    handStyles = {
      height: '130px',
      top: '70px',
    }
  }
  if (format === 'm') {
    handStyles = {
      height: '180px',
      top: '20px',
    }
  }
  if (format === 's') {
    handStyles = {
      height: '180px',
      top: '20px',
      width: '2px',
    }
  }

  const SSecHand = styled.div`
    width: 4px;
    background-color: #ffffff;
    border-radius: 100em;
    position: absolute;
    left: 50%;
    transform-origin: bottom center;
    transform: translateX(-50%) rotate(${Angle}deg);
    ${handStyles}
  `
  return <SSecHand />
}
export default Hand

まとめ

簡単な時計を作るだけでしたが、カスタムフックや繰り返し処理、コンポーネント分割の考慮など幅広く学習できたかと思います。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7