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

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

![clock.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1006362/b456d872-9f6a-e16b-560d-be115d10fa9d.gif)

## 利用技術
- emotion/react 11.8.2
- emotion/styled 11.8.1
- react 17.0.2
- typescript 4.6.3
- dayjs 1.11.0

## リンク先について
[GitHub](https://github.com/Naughty1029/React-Clock)でソースを公開しています。issue/プルリク募集しています！
またデモについては[こちら](https://react-clock.cocoroworks.net/)で公開しています。

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

### ディレクトリ構成について
下記構成でディレクトリをつくりました。
最近、みた海外の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として渡します。


```tsx: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を更新して返すようにしています。

```tsx: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: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: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キーは針の角度が何分割されるか表します。
１週は３６０度なので、それぞれ適切な分割数で割ります。

```tsx: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プロパティで角度を変えるようにしました。

```tsx: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
```

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