🗓️

時間割表を作成するサイトをバージョンアップ!

2022/05/14に公開

※前回の「時間割表を作成するアプリをReact.Jsで作ってみた」という記事の続きです。
前回の記事:
https://zenn.dev/501a/articles/1772f7ce5aa384

時間割表を作成するサイトDeizuをバージョンアップさせました!

Deizuのご紹介

実際のサイト:
https://deizu.vercel.app/

時間割画面:
Deizu使用例

大きく分けて2つの変更を加えました:

  • create-react-appからNext.Jsの使用
  • APIリクエストの減少(全体的に効率的な処理への改善)

それぞれについての具体的な説明をする前にそもそも何でDeizuを作ろうかと思ったのかについての説明をします:

Deizuを作ったきっかけ:

Deizuを作ったきっかけとしてはコロナウイルの影響でオンライン授業等に対応して学校の時間割表が何度も何度も変わりました。そこで学校から渡された時間割は学年の時間割が記載された非常に見にくいスプレッドシートでした。学校から渡された時間割表を解読し、クラスのみんながわかりやすく見れるものに変える役目を自分で担いましたw。頻繁なペースで時間割表を作成していたので、時間割表を作成できるアプリはないのかと思い、色々と調べました。ウェブ上で使用でき、満足できる見た目で時間割表の作成に特化ものは一つもなかったのでDeizuを作りました(当時は「Schedule Creator」という名前でした)。
初期バージョンが完成しDeizuを使って時間割表を作成するのは便利で良かったのですが、作った時間割表を他の人と共有したい度にスクリーンショットを撮りみんなに送信していました。
Google Docsのように自分が作成したドキュメントをリンクを通して共有できれば良いなと日々思いながらCRAで開発を進めていました。CRAはそもそもSPAにしか向いていないのでリンクを通した共有は少し難しいなと思いました。いずれラウト等を作成できるNext.Jsを使用すると良いのだなと思いましたが、その段階では既にプラットフォームとしてもう成り立っていったので作り直すのに非常に抵抗を感じていました。しかし、DeizuをZennとTwitterでしかPRしなかったのにも関わらず1月に入り沢山の新規ユーザーが増えました。(Google Analyticsもいれたつもりだったのですが使い方が間違っていたためユーザーがどこから来ていたのかがわかりませんでした、、w)ユーザーが増えるに連れプラットフォームをちゃんとした物にするべきという思いが湧き、今の状況で追加できる機能が制限されるぐらいであればNextで作り直すべきだと思いました。
最終的には紹介サイト(Next.Js)の上から作り直しました。

※「時間割 サイト」と調べると前書いたZennの記事が二番目に表示されるので、DeizuのユーザーはほぼZennからきたと思われます。ZennのSEO対策には感謝しかないですw。

create-react-appからNext.Jsの使用

初回に投稿したZennの記事では:
「Next.Jsという存在を認識しなかったため、現在は紹介サイトがNext.Jsと実際のウェブアプリがCRAでできておりコードベースが共有されていない変な状態です。今の所問題は無いのですが、ウェブアプリの方も必要であればNext.Jsに移したい思っています。」
と書きました(初回の記事のリンクは一番上に貼ってます)。

v2.0.0の初期はCRAで続けて作ろうかなと思いましたが、ユーザーが増えるに連れこのプラットフォームに対する思いがもっと強くなり結果的にはNextで作り直して本当に良かったと思ってます。

当時書いたツイート:

Deizuは初心者としてReact.Jsを本格的に使えたプロジェクトでした。初めてであったということで、とりあえず動けば良い思考でいたので全てにおいて無駄な要素がありました。(それについて詳しくは次のセクションで書きます)

APIリクエストの減少(全体的に効率的な処理への改善)

今思うと最初のDeizuは色々酷かったです。
まずは時間割表のセルはダイナミックレンダリングせずにそのままグリッドの中に書き込んでいたところです。

v1.0.0:

<section className="scheduleGrid">
 ...
	<TimeLabel num="1" sTitle={sheetTitle} selectorColor={selectorColorProps}/>
	<ScheduleCell cellId={"a1"} sTitle={sheetTitle}/>
	<ScheduleCell cellId={"b1"} sTitle={sheetTitle} />
	<ScheduleCell cellId={"c1"} sTitle={sheetTitle} />
	<ScheduleCell cellId={"d1"} sTitle={sheetTitle} />
	<ScheduleCell cellId={"e1"} sTitle={sheetTitle} />
	<ScheduleCell cellId={"f1"} sTitle={sheetTitle} />
...x7
</section>

これに加えセル(TimeLabelScheduleCell)それぞれにはモーダルが含まれていたので完成した状態のアプリをDev Toolsで見るとHTMLがものすごい大きさでした。

構造としてはシンプルでしたが一つ一つのセルが流石にデカすぎると感じました。セルにはそれぞれ、Firestoreからデータを取得する関数とFirestoreに書き込む関数がありました。(データの書き込みはcellIdのプロパティと時間割表のタイトルsTitleの情報をもとにFirestoreに保存していました。)

当時は何も感じませんでしたがよくよく考えれば、時間割表を開いている度に時間割のセルはそれぞれ異なるAPIリクエストをFirestoreに送信し、時間割表の全てのセルを記入する者がいれば最大49個のリクエストを送信していることになります(時間割の科目セル最大42個+時間軸のセル7個で49個)。これに、保存する時にリアルタイムで時間割表に表示させたかった為、保存ボタンを押すたび入力済のセルの全てからFirestoreへのAPIリクエストが再び送信されていました。

これを元に治したいと思った問題は、

  • 時間割セルの表示方法
  • セルの容量の大きさ
  • リクエスト数の削減

時間割セルの表示方法

時間割セルの表示は最初でも少し書いた通り、ダイナミックレンダリンクをすれば効率的に表示できます。scheduleCellIdはセルを区別化するID(a1,a2,a3...)が全て保存されている配列です。

<div style={scheduleGridStyle}>
{scheduleCellId.map(cellId =>                
  <SubjectCell
    sheetCellsData = {sheetCellsData}
    onClick={()=>{
      openCellModal(cellId);
      setModalCellId(cellId);
    }}
    cellId={cellId}
  />
)}
</div>

セルの容量の大きさ

セルの大きさを減少するには先ず、セルそれぞれに含まれているモーダルを取り出す必要があると考えました。セルそれぞれからモーダルを表示する必要はまずありません(モーダルをいっぺんに表示する訳ではないので)。モーダル(とデータの書き込み関数)はセルの中ではなくその親コンポーネントにおきました。上記より、cellIdプロパティーを用いてモーダルを開く関数にこのデータを受け渡し、どのセルにデータを書き込んでいるかを区別化することができます。これでセルに関するロジックは全て親コンポーネントにまとめることができ、セルコンポーネント自体は最小限な状態にすることができました。

リクエスト数の削減

リクエスト数の多さの問題は「時間割表を開くときにセルがそれぞれデータの取得を行なっているから」と「保存をするたびにリアルタイムなエフェクトを出すためにセルが再びリクエストを送信する」の二つの原因があります。

それらをどう改善したかをまとめました:

セルごとのデータの取得改善:

親コンポーネントからAPIコールを行うことで必要なデータは全て取得できますが、これをどう表示するかという問題があります。「時間割セルの表示方法」を改善するにあたって親コンポーネントでは既にセルのIDが受け渡すことができているので、これと同時に親コンポーネントから取得したデータをプロパティーを通して受け渡せます。cellIdを用い、取得したデータをドットノーテーションで確定できます。

let sheetCellsData = props.sheetCellsData;
let cellId = props.cellId
subjectCellName = sheetCellsData[cellId][cellId];

このように科目の名前を取得でき、同じように、色、URL、概要等の情報も取得できます。

APIコールを使わずにリアルタイムな感じを出す方法:

最後に保存をする度にFirestoreからデータの取得を求めて表示するのもやめました。科目に関するデータは全て(上記でも書いている通り)sheetCellsDataのステート(オブジェクト)に保存されているので、保存するたびに新しく入力したデータを追加すればAPIコールをせずにリアルタイムで表示されている雰囲気を出せます。

let newObject = Object.assign({ ...sheetCellsData, 
  [modalCellId]: {
    [modalCellId]: subjectCellName,
    [modalCellId + 'Link']: subjectCellLink,
    [modalCellId + 'Dscrp']: subjectCellDescription,
    [modalCellId + 'Color']: subjectCellColor
  } 
})
setSheetCellsData(newObject);

modalCellIdはモーダルを開くときに受け渡されたcellIdのプロパティーです

これから先Deizuでやっていきたいこと

CSRからSSGまたはSSRに切り替え

Next.Jsを使ったのにSSG・SSRを使わず実際は、CSRで完結しちゃいました。そのため、Next.Jsの良さを完全に取り入れていない気もするのでまたバージョン3ではSSGまたはSSRでFirestoreへのAPIリクエストを送信するなどしたいと思います。

デザインをより良くする

v2.0.0を作るにあたってデザインをもっとより良いものにしたいなと思いました。何事も作るにあたってデザインの部分に手掛けるのは個人的には非常に好きです。これからも、自分が気に入ったデザインをDeizuに取り入れつつ、自分なりの変更を加えながらオリジナルな物を作っていきたいなと思いました。

ツイッターを頻繁に見るようになってからさまざまな素晴らしいデザインのプロダクトと出会えたきっかけがもらえました。使ってもいないのにデザインが素敵だから使ってみたい気持ちが満載ですw。

いくつかご紹介:
https://cron.com/
https://dona.ai/
https://texts.com/

ここまで読んでくれてありがとうございます。
もし良ければDeizuを使ってみてください。

Discussion