はじめに(読まなくてもいい)
- タイトルの通り。私事ですがこの度妊娠し、産休期間を使ってどうにかReactの勉強がしたい、がはじまり
- 出産したら本家ぴよログ様を愛用する気ではあるが、これを参考にしたWebアプリを自分でも作ってみたら結構達成感味わえるのでは!?
- ということで、日々デカくなる腹とその影響による息切れと戦いながら少しずつ作成することに
- この記事は日時カレンダー(タイムスケジュール)表示ライブラリ選定をし、サンプルを表示するところまでをまとめる(体調と折り合いつけながら実施してる上、React初心者マンなので進捗は牛歩)
環境と環境構築
- 以前の記事を参考のこと
候補
- ググっても一つくらいしか候補が出なかったのでChatGPTに聞いた
react-big-calendar※採用
- 自分のやりたいこと(他ライブラリ却下時の理由に記載)ができそうなので採用
FullCalendar
- 結構有名なのか、ググって真っ先に出てきたのがこれ
- 無料プランだと制限があり、私がほしい「日時カレンダー」は有料版でないと表示できなさそう
- 故に却下
react-calendar-timeline
- 公式のGetting Startに沿ってサンプル実装まではできた
- ただ、今回のWebアプリはスマホオンリー(レスポンシブル!?うるせえ!!初心者に多くを求めるな!!!)で使用予定なので、横ではなく縦に時間が表示されるようにしたかった
- 調べてみたけど上記ができなさそう(できるにしても無理やりCSSの改造が必要でめんどくさそう)だった
- 故に泣く泣く却下
いざサンプル実装
パッケージインストール
- まずはreact-big-calendar
npm i react-big-calendar
- 言語対応のために使う日付フォーマットで
date-fns
を使用するためdate-fns
もインストール
npm i date-fns
公式のサンプルを持ってくる
- 公式が自らReact+Typescript+Viteの場合のサンプルを載せてくれている。神としか思えない
- 「Gitからクローンして動かして使ってね」と言われているが、私が作っているアプリにそのままぶちこみ、読み解きに使わせてもらうぜ!
エラー対応をする
- ただ、公式サンプルの
App.tsx
の内容をそのまま貼り付けただけではエラーが出たので以下の通り修正している
インポート文
- 修正前
App.tsx
import addHours from 'date-fns/addHours'
import startOfHour from 'date-fns/startOfHour'
import format from "date-fns/format";
import getDay from "date-fns/getDay";
import parse from "date-fns/parse";
import startOfWeek from "date-fns/startOfWeek";
- 修正後
App.tsx
import {
addHours,
format,
getDay,
parse,
startOfHour,
startOfWeek,
} from "date-fns";
- 修正前だと、以下のエラーが発生する
この式は呼び出し可能ではありません。
型 'typeof import("/node_modules/date-fns/addHours")' には呼び出しシグネチャがありません
- どうやら、
date-fns
のモジュールがインポート方法に依存することがあるため、default importかnamed importを明確に指定する必要があるとか - 修正前はdefault importだったので、named importに変えてみたところ、解決した
- 推論だが、インポート方法を変えたことで明確に「addHours」や「startOfHour」の関数を指定している状態になり、解決できたのかなあ(有識者の方に教えてほしい)
Appコンポーネントの書き方
- 別にエラーが発生していたわけではないが、書き方が古いらしい(夫が教えてくれた)ので、修正
- 修正前
App.tsx
const App: FC = () => {
}
- 修正後
App.tsx
const App = () => {
}
- FCというのは、「React.FunctionComponent」のことで、関数コンポーネントの型定義をするための仕組み
- Reactのv16らへんではよく使われていたっぽい(自分でQiitaだのZennの記事をさらっと読んだ限り)
- 以下の特徴を持つらしい
- 引数(props)の型を指定することができる(Typescriptでできる)
- children の型が自動的に追加される(これにより、props.children を型チェックしながら扱える)
- 関数コンポーネントであることを TypeScript に明示できる(クラスコンポーネントではないことを保証する)
- JSX.Element(修正後のかたち)にはない静的プロパティたちが使える
- とはいえそこまでメリットではないとのこと
-
props に children プロパティが自動的に追加されるので、props.children を明示的に指定する必要がない- Reactのv18から暗黙的なChildrenはエラーになったので影響なし
- 現在はFCなぞ使わずJSX.Elementを使うのが一般的っぽい
参考:https://kray.jp/blog/dont-have-to-use-react-fc-and-react-vfc/
https://zenn.dev/motonosuke/articles/13d0fdd417ec7c
returnの位置
※最後に完成版App.tsx
載せているので修正前後の記載は省略
- 公式はreturnしたあとに変数だの何だのを定義していて気持ち悪い
- 私は「returnは最後に記載しろ過激派」のため、returnする箇所より前に定義している部分を持ってくる
日本語対応をする
- ここまででも一応表示はできるのだが、日本語の日時にしたいので日本語対応を行う
- 公式の英語ロケーション定義から日本語のロケーション定義に変更
- 修正前
App.tsx
import enUS from 'date-fns/locale/en-US'
// 中略
const locales = {
'en-US': enUS,
}
- 修正後
App.tsx
import { ja } from "date-fns/locale";
// 中略
const locales = {
ja: ja,
};
- 日付表示対応と、フォーマットの設定を追加
- 修正前
App.tsx
return (
<DnDCalendar
defaultView='week'
events={events}
localizer={localizer}
onEventDrop={onEventDrop}
onEventResize={onEventResize}
resizable
style={{ height: '100vh' }}
/>
)
- 修正後
App.tsx
return (
<DnDCalendar
defaultView="day"
events={events}
localizer={localizer}
// ----追加部分----↓
formats={{
dayHeaderFormat: "yyyy年MM月dd日(EEEE)",
}}
culture={"ja"}
// ----追加部分----↑
onEventDrop={onEventDrop}
onEventResize={onEventResize}
resizable
style={{ height: "100vh" }}
/>
);
参考:https://qiita.com/KyongminS/items/2ae488969d631e795c64
最終成果物
- まあここだけ見れば最悪OK
App.tsx
import {
addHours,
format,
getDay,
parse,
startOfHour,
startOfWeek,
} from "date-fns";
import { ja } from "date-fns/locale";
import { useState } from "react";
import { Calendar, dateFnsLocalizer, Event } from "react-big-calendar";
import withDragAndDrop, {
withDragAndDropProps,
} from "react-big-calendar/lib/addons/dragAndDrop";
import "react-big-calendar/lib/addons/dragAndDrop/styles.css";
import "react-big-calendar/lib/css/react-big-calendar.css";
const App = () => {
const locales = {
ja: ja,
};
const endOfHour = (date: Date): Date => addHours(startOfHour(date), 1);
const now = new Date();
const start = endOfHour(now);
const end = addHours(start, 2);
// The types here are `object`. Strongly consider making them better as removing `locales` caused a fatal error
const localizer = dateFnsLocalizer({
format,
parse,
startOfWeek,
getDay,
locales,
});
const DnDCalendar = withDragAndDrop(Calendar);
const [events, setEvents] = useState<Event[]>([
{
title: "Learn cool stuff",
start,
end,
},
]);
const onEventResize: withDragAndDropProps["onEventResize"] = (data) => {
const { start, end } = data;
setEvents((currentEvents) => {
const firstEvent = {
start: new Date(start),
end: new Date(end),
};
return [...currentEvents, firstEvent];
});
};
const onEventDrop: withDragAndDropProps["onEventDrop"] = (data) => {
console.log(data);
};
return (
<DnDCalendar
// ぜんこで気づいたかもだが、最初に表示されるカレンダーを「day」にしたいのでweek→dayに修正
defaultView="day"
events={events}
localizer={localizer}
formats={{
dayHeaderFormat: "yyyy年MM月dd日(EEEE)",
}}
culture={"ja"}
onEventDrop={onEventDrop}
onEventResize={onEventResize}
resizable
style={{ height: "100vh" }}
/>
);
};
export default App;
- これさえ実装してしまえば、前述したサンプルが表示される(はず)
今後の課題について
- とりあえず、日時カレンダー表示はできたところで、現状で今後やりたいことや課題をまとめる
- まず公式サンプル読み解きは必須
- 公式リファレンス参考に、以下列挙する課題解決できるような設定内容を模索していく予定
- 2人分のスケジュールが表示できるようにする
- 実は双子妊娠中のため、双子に対応できるよう2人分を横に並べて表示できるようにしたい
- 色分けをするなど、可視化しやすく
- スケジュール(という名の育児記録)登録ができるようにする
- 最低限で時間と何をしたかの登録ができるようにする
- できれば、「何をしたか」の部分はミルクの量などのプロパティやカテゴリー分け(ミルク、おむつ交換など)にも対応させておきたいところ
- 双子同時登録も対応できたらいいなあ
- あくまで現状はサンプルを構築しただけなので、以下は順次対応していく
- ディレクトリ構成の見直し
- Typescript、React観点で記述に問題ないか?の確認と修正(初心者故の課題)
- FCを使っているなど古い書き方になっていないか?
- 型の宣言できていないところなどないか?
- 書き方が不適切な部分はないか?など