ReactApps

2019年3月2日

1ヶ月のカレンダーをつくる

3jueczvyc

はじめに

ローカル環境でこのサンプルを動かすには以下の手順に従ってください。

$ git close git@github.com:reiwa/reactapps.git
$ cd reactapps
$ yarn
$ yarn workspace calendar start

※ $の部分はペーストしないでください。

内容

汎用性の高い1ヶ月のカレンダーをつくります。
次または前の月に切り替えることができ、1月より前に戻ろうとすれば前の年に切り替わります。ToDoリストやスケジュール管理など日付を扱うときカレンダーはよく用いられます。またカレンダーの用途は様々過ぎるのでライブラリを用いずに実装出来ると良いです。

コード

import React, { Fragment, FunctionComponent, useState } from 'react'

const App: FunctionComponent = () => {
  const [year, setYear] = useState(new Date().getFullYear())

  const [month, setMonth] = useState(new Date().getMonth() + 1)

  const calendar = createCalendar(year, month)

  const onClick = (n: number) => () => {
    const nextMonth = month + n
    if (12 < nextMonth) {
      setMonth(1)
      setYear(year + 1)
    } else if (nextMonth < 1) {
      setMonth(12)
      setYear(year - 1)
    } else {
      setMonth(nextMonth)
    }
  }

  return (
    <Fragment>
      <h1>{`tháng ${month} năm ${year}`}</h1>
      <div>
        <button onClick={onClick(-1)}>{'prev'}</button>
        <button onClick={onClick(1)}>{'next'}</button>
      </div>
      <table>
        <tbody>
          {calendar.map((week, i) => (
            <tr key={week.join('')}>
              {week.map((day, j) => (
                <th key={`${i}${j}`}>{day}</th>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </Fragment>
  )
}

const createCalendar = (year: number, month: number) => {
  const first = new Date(year, month - 1, 1).getDay()

  const last = new Date(year, month, 0).getDate()

  return [0, 1, 2, 3, 4, 5].map((weekIndex) => {
    return [0, 1, 2, 3, 4, 5, 6].map((dayIndex) => {
      const day = dayIndex + 1 + weekIndex * 7
      return day - 1 < first || last < day - first ? null : day - first
    })
  })
}

export default App

いくつかポイント

1ヶ月のカレンダーを配列として「週」を「列」として扱う2次元配列で表現できます。入力として必要なのは年(year: number)と月(month: number)の2つです。

const createCalendar = (year: number, month: number) => {
  const first = new Date(year, month - 1, 1).getDay()
  const last = new Date(year, month, 0).getDate()
  return [0, 1, 2, 3, 4, 5].map(weekIndex => {
    return [0, 1, 2, 3, 4, 5, 6].map(dayIndex => {
      const day = dayIndex + 1 + weekIndex * 7
      return day - 1 < first || last < day - first ? null : day - first
    })
  })
}

Web APIを用いてから月の最初と最後の日が週の何日目なのかを計算します。

const first = new Date(year, month - 1, 1).getDay()
const last = new Date(year, month, 0).getDate()

最後の無名関数では「最初の日以前」と「最後の日以降」をnullとして返しています。よって、この無名関数は返り値は(number | null)[][]と推論されます。

return day - 1 < first || last < day - first ? null : day - first

このように使用します。

const calendar = createCalendar(year, month)

あとはyearmonthをコンポーネントの状態にして更新するだけで1ヶ月のカレンダーが再現できます。