SNSによくある経過時間を表示するコンポーネントをvueで実装してみた

TwitterやFacebookなどで見かける「投稿してから○分経過」のようなコンポーネントを作成しました。
![lastTime.gif](https://qiita-image-store.s3.amazonaws.com/0/16535/19bd0564-dad9-22d6-fb03-4622b7e31992.gif)

`setInterval()` を使用し、vue.jsとTypescriptで実装しています。表示する時間の単位によってインターバルの間隔を変更しています。ほぼTypescriptなので、他のjavascriptフレームワークにも簡単に移植出来ると思います。

# 全体像

```vue:src/components/LastTime.vue
<template>
  <div>{{ lastTime }}</div>
</template>

<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator'

enum IntervalTime {
  SEC = 1000,
  MIN = 60 * IntervalTime.SEC,
  HOUR = 60 * IntervalTime.MIN,
  DAY = 24 * IntervalTime.HOUR,
  WEEK = 7 * IntervalTime.DAY,
  YEAR = 52 * IntervalTime.WEEK,
}

enum TimeUnit {
  SEC = 'sec',
  MIN = 'min',
  HOUR = 'hour',
  DAY = 'day',
  WEEK = 'week',
  YEAR = 'year',
}

@Component
export default class LastTime extends Vue {
  @Prop({ default: () => 0 }) baseTime!: number
  duration: number = 0
  intervalId: number = 0
  intervalTime: IntervalTime = IntervalTime.SEC
  timeUnit: TimeUnit = TimeUnit.SEC

  created() {
    const currentTime = new Date().getTime()
    this.duration = currentTime - this.baseTime
    this.intervalId = setInterval(() => {
      this.timer()
    }, this.intervalTime)
  }

  timer() {
    const currentTime = new Date().getTime()
    this.duration = currentTime - this.baseTime
    if (this.duration > IntervalTime.YEAR) {
      this.restartInterval(IntervalTime.YEAR, TimeUnit.YEAR)
    } else if (this.duration > IntervalTime.WEEK && this.intervalTime <= IntervalTime.DAY) {
      this.restartInterval(IntervalTime.WEEK, TimeUnit.WEEK)
    } else if (this.duration > IntervalTime.DAY && this.intervalTime <= IntervalTime.HOUR) {
      this.restartInterval(IntervalTime.DAY, TimeUnit.DAY)
    } else if (this.duration > IntervalTime.HOUR && this.intervalTime <= IntervalTime.MIN) {
      this.restartInterval(IntervalTime.HOUR, TimeUnit.HOUR)
    } else if (this.duration > IntervalTime.MIN && this.intervalTime <= IntervalTime.SEC) {
      this.restartInterval(IntervalTime.MIN, TimeUnit.MIN)
    }
  }

  restartInterval(intervalTime: IntervalTime, timeUnit: TimeUnit) {
    clearInterval(this.intervalId)
    this.intervalTime = intervalTime
    this.timeUnit = timeUnit
    this.intervalId = setInterval(() => {
      this.timer()
    }, this.intervalTime)
  }

  get lastTime(): string {
    let divide: IntervalTime = IntervalTime.SEC
    if (this.duration < IntervalTime.MIN) {
    } else if (this.duration < IntervalTime.HOUR) {
      divide = IntervalTime.MIN
    } else if (this.duration < IntervalTime.DAY) {
      divide = IntervalTime.HOUR
    } else if (this.duration < IntervalTime.WEEK) {
      divide = IntervalTime.DAY
    } else if (this.duration < IntervalTime.YEAR) {
      divide = IntervalTime.WEEK
    } else {
      divide = IntervalTime.YEAR
    }
    return Math.floor(this.duration / divide) + ' ' + this.timeUnit
  }
}
</script>

<style lang="scss" scoped></style>
```

# 設計のキモ
## enum IntervalTime
```ts
enum IntervalTime {
  SEC = 1000,
  MIN = 60 * IntervalTime.SEC,
  HOUR = 60 * IntervalTime.MIN,
  DAY = 24 * IntervalTime.HOUR,
  WEEK = 7 * IntervalTime.DAY,
  YEAR = 52 * IntervalTime.WEEK,
}
```
時間の処理を書く時によく書きがちな`60 * 60 * 1000`のような計算を、enumを使って定義しています。どのような計算をしているか一目で分かると思います。このように書くことで「掛ける値の桁が足りてなかった！」などといったちょっとしたミスを減らすことができます。

## restartInterval()
```ts
restartInterval(intervalTime: IntervalTime, timeUnit: TimeUnit) {
  clearInterval(this.intervalObj)
  this.intervalTime = intervalTime
  this.timeUnit = timeUnit
  this.intervalObj = setInterval(() => {
    this.timer()
  }, this.intervalTime)
}
```
分表記や時間表記の時に`1000ms`で再描画しても無駄なので、経過時間によって、`setInterval()`の間隔を長くします。

## get lastTime()
```ts
get lastTime(): string {
  let divide: IntervalTime = IntervalTime.SEC
  if (this.duration < IntervalTime.MIN) {
  } else if (this.duration < IntervalTime.HOUR) {
    divide = IntervalTime.MIN
  } else if (this.duration < IntervalTime.DAY) {
    divide = IntervalTime.HOUR
  } else if (this.duration < IntervalTime.WEEK) {
    divide = IntervalTime.DAY
  } else if (this.duration < IntervalTime.YEAR) {
    divide = IntervalTime.WEEK
  } else {
    divide = IntervalTime.YEAR
  }
  return Math.floor(this.duration / divide) + ' ' + this.timeUnit
}
```
`computed`で時間の単位とともに経過時間を出力します。

# その他
細かいところはもっと改良できそうな気がするのでお気づきの点があればプルリクもしくはコメント下さい！

:octocat: [MssKnd/LastTime](https://github.com/MssKnd/LastTime)