FullCalendarで簡単なカレンダーを作る
はじめに
fullcalendarには色々とカレンダーの表示方法がある。一番シンプルなのものをメモしておく。
(1)一番シンプルな月単位カレンダーを作る
インストール
まずライブラリをインストール。
npm install @fullcalendar/react @fullcalendar/core @fullcalendar/daygrid
React Component - Docs | FullCalendar
コード
mport React from 'react'; import FullCalendar from '@fullcalendar/react'; import dayGridPlugin from "@fullcalendar/daygrid"; const Calendar = () => { return ( <FullCalendar plugins={[dayGridPlugin]} initialView="dayGridMonth" /> ); } export default Calendar;
plugins={[dayGridPlugin]}
このpluginを指定することで、月次カレンダーが表示されるようになる。initialView="dayGridMonth"
pluginsでdayGridPluginを指定した時の、初期表示ビューの設定。 dayGridMonthで月単位で表示する標準的なグリッドビューとなる。
他にも、dayGridWeek
dayGridDay
結果
(2)時間単位のカレンダーを作る
インストール
npm install @fullcalendar/timegrid
コード
import React from 'react'; import FullCalendar from '@fullcalendar/react'; import timeGridPlugin from "@fullcalendar/timegrid"; const Calendar = () => { return ( <FullCalendar plugins={[timeGridPlugin]} initialView="timeGridWeek" /> ); } export default Calendar;
結果
参考にしたサイト
pluginから理解するFullCalendar with React #UI - Qiita
React + TypeScript + FullCalendar: 簡単なカレンダーをつくる #JavaScript - Qiita
型ガード is演算子について
is演算子とは
正式名称「ユーザー定義型ガード(User-Defined Type Guards)」といい、自分で定義した型に対して型ガードを適用する際に使う。 順番に説明していく。
型ガードとは
TypeScriptの機能であり、特定の型が使われているかを判定することができる。
型ガードを使うことで、型エラーを事前に防ぐことができるため、コードを安全に書ける。
型ガードは大きく分けて2種類あり、 「組み込み型ガード」 、「ユーザー定義型型ガード」がある。
組み込み型の型ガード
TypeScriptの組み込み型に対して使用する型ガードである。
typeof
プリミティブ型(string, number, boolean, symbol, object等)を判定する際に使用される。
function getType(value: string | number) { if (typeof value === "string") { console.log("This is a string!"); } else { console.log("This is a number!"); } } getType("Hello, TypeScript!"); // This is a string! getType(42); // This is a number!
instanceof
オブジェクトが特定のクラスのインスタンスであるかどうかを判定する。
class Person { constructor(public name: string) {} } class Animal { constructor(public species: string) {} } function printInfo(value: Person | Animal) { if (value instanceof Person) { console.log(`こんにちは、 ${value.name}!`); } else { console.log(`これは ${value.species} です`); } } printInfo(new Person("ジョン")); // こんにちは、ジョン! printInfo(new Animal("犬")); // これは 犬 です
ユーザー定義型型ガード
こっちが本題。自分で定義した型に対する型ガードはこちらを使う。
自分で定義した型かどうか判定する「関数」を定義することで実現できる。
// 2種類の型を定義 interface User { name: string; type: "user"; } interface Admin { name: string; type: "admin"; permissions: string[]; } // ユーザーのデータがどちらの型か判定するカスタム型ガード関数 function isAdmin(user: User | Admin): user is Admin { return user.type === "admin"; } // 実際に使ってみる const john: User = { name: "John", type: "user" }; const jane: Admin = { name: "Jane", type: "admin", permissions: ["read", "write"] }; // 判定 console.log(isAdmin(john)); // false console.log(isAdmin(jane)); // true
isAdmin関数は、User型かAdmin型かの引数user
を受け取り、もじtypeフィールドが"admin"であればtrueを返す。
user is Admin
を関数isAdminの戻り値の型定義として付けることで、このisAdmin関数は型を判定するものなんだとTypeScriptに伝えている(多分)。
このように、is演算子を使用した関数を定義した場合、必ずtrueかfalseを戻り値とする必要がある。そうでないとコンパイルエラーになる。
参考にしたサイト様
typeofの使い方
Typeofとは
typeof
はJavaScriptとTypeScriptで使われる、型を調べるための演算子である。
使い方
console.log(typeof "Hello"); // "string" console.log(typeof 123); // "number" console.log(typeof true); // "boolean" console.log(typeof {}); // "object" console.log(typeof []); // "object" // 配列も「object」と表示される console.log(typeof null); // "object" // `null` も「object」と表示される console.log(typeof undefined); // "undefined" console.log(typeof function(){}); // "function"
typeofを型定義につかう
こういう使い方も可
const person = { name: "Alice", age: 25 }; type PersonType = typeof person; const anotherPerson: PersonType = { name: "Bob", age: 30 };
Firestoreからデータを取得してReactで使用する方法
はじめに
Firestoreからデータを取得して、Reactコンポーネントで使用する流れを、簡単な具体例で説明する。
この例では、Firestoreに保存されている「ユーザー」データを取得し、それをReactコンポーネントに表示する方法を解説する。
前提
- Firestoreに「users」というコレクションがあり、各ユーザードキュメントに
name
とage
のフィールドがある。
ステップ1 Firebase SDKをインストール
ReactプロジェクトにFirebase SDKをインストールし、Firestoreと連携できるように設定を行う。
npm install firebase
ステップ2 Firestore設定ファイル(firebase.ts)を作成
次に、Firestoreの接続設定を行うためのfirebase.ts
(typescriptを使っているので.ts
)ファイルを作成し、Firebaseプロジェクトと接続する。
プロジェクトのsrcディレクトリにfirebase.ts
ファイルを作成し、以下のように記述する。
設定情報については、firestoreの「プロジェクトの設定」に書いてある。
// src/firebase.ts import { initializeApp } from "firebase/app"; // Firebase の初期化 import { getFirestore } from "firebase/firestore"; // Firestore インスタンスの取得 // ここに自分の Firebase プロジェクトの設定情報を記述 const firebaseConfig = { apiKey: "YOUR_API_KEY", authDomain: "YOUR_PROJECT_ID.firebaseapp.com", projectId: "YOUR_PROJECT_ID", storageBucket: "YOUR_PROJECT_ID.appspot.com", messagingSenderId: "YOUR_MESSAGING_SENDER_ID", appId: "YOUR_APP_ID" }; // Firebase を初期化 const app = initializeApp(firebaseConfig); // Firestore インスタンスを作成してエクスポート export const db = getFirestore(app);
ステップ3 Firestoreインスタンスを使用する。
firestoreのデータを使用したい箇所で下記を記述
import React, { useEffect, useState } from 'react'; import { collection, getDocs } from "firebase/firestore"; // Firestore の関数をインポート import { db } from '../firebase'; // `db` インスタンスをインポート export interface userType { id: string, name: string, age: number, } const UserList = () => { const [users, setUsers] = useState<userType[]>([]); // 初期状態は空の配列 useEffect(() => { const fetchUsers = async () => { try { // Firestore から "users" コレクションを取得 const querySnapshot = await getDocs(collection(db, "users")); // データを配列形式にマッピング const usersData = querySnapshot.docs.map((doc) => { return { ...doc.data(), // ドキュメントのデータを展開 id: doc.id, // ドキュメント ID を追加 } as userType; }); setUsers(usersData); // 状態を更新 } catch (error) { console.error("Error fetching users: ", error); } }; fetchUsers(); // データ取得関数を呼び出し }, []); return ( <div> <h1>ユーザーリスト</h1> <ul> {users.map((user) => ( <li key={user.id}> 名前: {user.name}, 年齢: {user.age} </li> ))} </ul> </div> ); }; export default UserList;
- getDocs(collection(db, "users"));の所がデータ取得のキモ。dbは
firebase.ts
で定義したdb(firestoreインスタンス)であり、"users"
はfirestore上のコレクション名。 querySnapshot
のデータ構造を下記で説明する。
querySnapshotの構造
QuerySnapshot | JavaScript SDK | Firebase JavaScript API reference
代表的なプロパティ
docs
:クエリで取得したドキュメントのリスト 。各要素はqueryDocumentSnapshot
オブジェクト。empty
:クエリ結果が空かどうかmetadata
:スナップショットのメタデータ情報size
:取得されたドキュメント数
querySnapshot.docsの構造
querySnapshot.docsの構造は以下のとおりである。
[ queryDocumentSnapshot, // ドキュメント 1 queryDocumentSnapshot, // ドキュメント 2 queryDocumentSnapshot, // ドキュメント 3 ... // 取得したドキュメント数に応じて繰り返し ]
QueryDocumentSnapshot | JavaScript SDK | Firebase JavaScript API reference
代表的なプロパティとメソッド
id
:ドキュメントの一意の ID。Firestore コレクション内でドキュメントを特定するための識別子。
querySnapshot.docs.forEach((doc) => { console.log("ドキュメント ID:", doc.id); });
data()
:ドキュメントのデータをオブジェクト形式で取得。
querySnapshot.docs.forEach((doc) => { console.log("ドキュメントデータ:", doc.data()); });
exists()
:ドキュメントが存在するかどうかをboolwan型で返す。削除されたり、存在しない場合はfalse。
querySnapshot.docs.forEach((doc) => { if (doc.exists) { console.log(`ドキュメント ${doc.id} は存在します。`); } else { console.log(`ドキュメント ${doc.id} は存在しません。`); } });
metadata
:ドキュメントのメタデータ情報(キャッシュから取得されたかどうか、サーバーとの同期状態など)。ref
:そのドキュメントの DocumentReference(参照)。collection メソッドを使ってそのドキュメントのサブコレクションにアクセス可能。
querySnapshot.docs.forEach((doc) => { console.log("ドキュメント参照:", doc.ref.path); });
get(fieldPath)
:特定のフィールドの値を取得。data() とは異なり、フィールドパスを指定して部分的にデータを取り出す。
querySnapshot.docs.forEach((doc) => { console.log("指定されたフィールドの値:", doc.get("fieldName")); });
querySnapshot.docsの具体例
実際のquerySnapshot.docs
の構造を以下の例で確認してみる。
例えば、FirestoreのUsersコレクションが以下のようなデータを持っているとする。
ドキュメントID | 名前(name) | 年齢(age) |
---|---|---|
user1 | Alice | 30 |
user2 | Bob | 25 |
user3 | Charlie | 35 |
この時、次のようなコードでUsersコレクションからデータを取得する。
const querySnapshot = await getDocs(collection(db, "Users")); console.log(querySnapshot.docs);
これによって、次のようなquerySnapshot.docs
の配列が取得される。
[ { "id": "user1", "exists": true, "ref": { /* ドキュメント参照情報 */ }, "data": { "name": "Alice", "age": 30 }, "metadata": { /* メタデータ情報 */ } }, { "id": "user2", "exists": true, "ref": { /* ドキュメント参照情報 */ }, "data": { "name": "Bob", "age": 25 }, "metadata": { /* メタデータ情報 */ } }, { "id": "user3", "exists": true, "ref": { /* ドキュメント参照情報 */ }, "data": { "name": "Charlie", "age": 35 }, "metadata": { /* メタデータ情報 */ } } ]
<Grid container> と <Grid item> の基本
はじめに
<Grid>
コンポーネントはMUIが提供する、グリッドレイアウトを実現するためのコンポーネントである。
<Grid container></Grid>
を親要素、<Grid item></Grid>
を子要素として指定することで、グリッドレイアウトを使用できる。
基本ルール
<Grid container></Grid>
の中に<Grid item></Grid>
を入れる。- アイテムの幅はxs, sm, md, lg, xlのようなbreakpointによって指定する。
具体例
<Grid container spacing={2} sx={{ padding: 2 }}> <Grid item xs={12} sm={6} md={4}>~</Grid> <Grid item xs={12} sm={6} md={4}>~</Grid> <Grid item xs={12} sm={12} md={4}>~</Grid> </Grid>
spacing
を<Grid container>
につけることで、子要素の間隔を調整できる。spacing={2}
により、子要素間に2の間隔(8px×2=16px)を設定している。sx={{ padding: 2 }}
は親要素全体に16pxの内側余白を設定している。xs={12}
は、画面がxs(最小サイズ)以上であった時、1行全体の幅を取るよう指定。sm={6}
は、画面がsm(smallサイズ)以上であった時、1行の半分の幅を取るよう指定。md={4}
は、画面がmd(Mediumサイズ)以上であった時、1行の1/3の幅を取るよう指定。
display: flexについて
はじめに
以前の記事でdisplay :
について書いたが、そこで説明しなかったflex
について調べた。
display : flexとは
displayプロパティはCSSプロパティの一つで、要素の表示方法(レイアウトの仕方)を指定するために使われる。
この辺りは以前の記事で書いたとおりである。
display : "flex"
と設定すると、その要素はフレックスコンテナといって、子要素を柔軟に配置できるようになる。
block
やinline
のように子要素側に設定するのではなく、親要素側に設定する。
具体例
前回同様具体例で説明する。まず適当なフォルダに「page.html」、「sample.css」を作成し、それぞれこのように記載する。
- page.html
<html> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="sample.css"> <title>testPage</title> </head> <body> <div class="parent"> <div class="child" style=~~>要素1</div> <div class="child" style=~~>要素2</div> <div class="child" style=~~>要素3</div> </div> </body> </html>
- sample.css
.parent { border: 1px solid black; /* 親要素の境界線を表示 */ padding: 10px; /* 親要素に余白を追加 */ background-color: green; /*背景色を追加*/ display: flex; } .child { border: 1px solid black; /* 子要素の境界線を表示 */ padding: 5px; /* 子要素に余白を追加 */ background-color: yellowgreen; /*背景色を追加*/ text-decoration: none; color: inherit; }
「style=~~」の部分にを色々変えて挙動の違いを確認する。
何も書かない
何も書かないと子要素はただの横並びになる。
flex-grow : ~
子要素がどれだけ伸びることができるかを指定する。
どれだけの比率にするかをそれぞれ決定できる。
- 子要素を1: 1: 1で並べたい場合、下記のように書く。
<div class="parent"> <div class="child" style="flex-grow:1">要素1</div> <div class="child" style="flex-grow:1">要素2</div> <div class="child" style="flex-grow:1">要素3</div> </div>
- 3: 1 : 2で並べたい場合、下記のように書く。
<div class="parent"> <div class="child" style="flex-grow:3">要素1</div> <div class="child" style="flex-grow:1">要素2</div> <div class="child" style="flex-grow:2">要素3</div> </div>
- 要素2と3は固定長で、要素1だけが画面幅に合わせて伸びるようにする場合、下記のように書く
<div class="parent"> <div class="child" style="flex-grow:1">要素1</div> <div class="child">要素2</div> <div class="child">要素3</div> </div>
参考にしたサイト
MUIのpaletteでカスタムプロパティを使用する方法
はじめに
前回の記事で別に書くと宣言していた件
今回はTypeScriptを使用する場合に、MUIのcreateTheme関数でカスタムプロパティを使用する方法を調べた。
説明
下記の例において、primaryとsecondaryはMUIで定義されるデフォルトプロパティであるが、hogeColor
は定義されていない。
const theme = createTheme({ palette: { primary: { main: '#1976d2' }, // メインカラーを青色に設定 secondary: { main: '#d32f2f' } // サブカラーを赤色に設定 hogeColor: {main: '#e0e0e0'} // ここが今回の焦点 }, });
この状態で実行すると「'hogeColor'は型 'PaletteOptions' に存在しません。」とエラーになる。
これ実はpaletteで使えるプロパティは
node_modules/@mui/material/styles/createPalette.d.ts
に下記のように型定義されている。
... export interface Palette { common: CommonColors; mode: PaletteMode; contrastThreshold: number; tonalOffset: PaletteTonalOffset; primary: PaletteColor; secondary: PaletteColor; error: PaletteColor; warning: PaletteColor; info: PaletteColor; success: PaletteColor; grey: Color; text: TypeText; divider: TypeDivider; action: TypeAction; background: TypeBackground; getContrastText: (background: string) => string; augmentColor: (options: PaletteAugmentColorOptions) => PaletteColor; } ... export interface PaletteOptions { primary?: PaletteColorOptions; secondary?: PaletteColorOptions; error?: PaletteColorOptions; warning?: PaletteColorOptions; info?: PaletteColorOptions; success?: PaletteColorOptions; mode?: PaletteMode; tonalOffset?: PaletteTonalOffset; contrastThreshold?: number; common?: Partial<CommonColors>; grey?: ColorPartial; text?: Partial<TypeText>; divider?: string; action?: Partial<TypeAction>; background?: Partial<TypeBackground>; getContrastText?: (background: string) => string; } ...
Palette
は実際のテーマの色情報を表すインターフェイスである。実際に色設定として使われる。
PaletteOptions
はcreateTheme関数でテーマを作成する際に使用するインターフェイスである。
このそれぞれにhogeColor
が記載されていなければ使用することができない。
カスタムプロパティを追加
上で定義されているPaletteとPaletteOptionsに直接hogeColor
を追記するのではなく、declare module
を使用して型定義を拡張する。
declare module '@mui/material/styles' { interface Palette { hogeColor: { main: string; }; } interface PaletteOptions { hogeColor?: { main?: string; }; } } const theme = createTheme({ palette: { primary: { main: '#1976d2' }, // メインカラーを青色に設定 secondary: { main: '#d32f2f' } // サブカラーを赤色に設定 hogeColor: {main: '#e0e0e0'} // ここが今回の焦点 }, });
参考にしたサイト
Material-UIのPaletteをカスタマイズした時の型定義について - TypeScript #React - Qiita