はじめに
React + Viteでタスク管理アプリとメモアプリを作った後、はじめてVue.js 3に挑戦し、
家計簿アプリ「家計簿」を作りました。
収支の記録・月ごとの集計・カテゴリ別グラフ表示という、これまでより一段複雑な
「集計処理」が中心のアプリです。
同じ課題をReactとVueの両方で解いてみることで、
「フレームワーク固有の書き方」と「フロントエンド設計の普遍的な考え方」を
切り分けて理解できたのが、今回一番の収穫でした。
GitURL
技術スタック
| 項目 | 使用technology |
|---|---|
| フレームワーク | Vue 3(Composition API, <script setup>) |
| ビルドツール | Vite |
| データ保存 | ブラウザの localStorage |
| グラフ表示 | 自作(CSSのみ、外部ライブラリなし) |
できること
- 収入・支出の記録(日付・カテゴリ・金額・メモ)
- 「◀前月」「翌月▶」による月の切り替え
- 月間の収入合計・支出合計・収支の表示
- カテゴリ別の支出内訳を横棒グラフで表示
- LocalStorageへの自動保存
ReactとVueの書き方の対応表
同じ「状態を作って画面に反映する」という目的でも、書き方はこれだけ変わります。
| やりたいこと | React | Vue 3 |
|---|---|---|
| 状態を作る | useState(初期値) |
ref(初期値)(読み書きは.value) |
| 複数項目をまとめた状態 | useState({...}) |
reactive({...}) |
| 自動計算される値 | useMemo(() => 計算, [依存]) |
computed(() => 計算)(依存は自動検出) |
| 値の変化に反応する | useEffect(() => {...}, [依存]) |
watch(値, (新値) => {...}) |
| 子から親へイベントを伝える | propsで関数を渡す | emit() |
| フォームと状態をつなぐ |
valueとonChangeを両方書く |
v-model一行で完結 |
特にv-modelは便利で、Reactで毎回2行に分けて書いていた
「値を表示する」「入力イベントで状態を更新する」を1つの属性に集約できます。
逆にVueのwatchで配列やオブジェクトの中身の変化を検知するには
{ deep: true }を明示する必要があり、ReactのuseEffectが
配列を新しく作り直すことで変化を伝えていたのとは違う設計思想を感じました。
実装のポイント
月をまたぐ計算は Date オブジェクトに任せる
export function shiftYearMonth(yearMonth, direction) {
const [year, month] = yearMonth.split("-").map(Number);
const date = new Date(year, month - 1 + direction, 1);
const newMonth = String(date.getMonth() + 1).padStart(2, "0");
return `${date.getFullYear()}-${newMonth}`;
}
「1月から前月に戻ると前年の12月になる」といった年またぎの計算を、
自分で条件分岐を書いて対応しようとするとバグが起きやすくなります。
一度Dateオブジェクトに変換して計算を任せ、また文字列に戻す方法にすることで、
分岐を書かずに正しく計算できました。
カテゴリ別の集計は、グラフコンポーネント自身に持たせた
これまでのReact版では「状態とロジックは親コンポーネントに集約する」方針を
徹底していましたが、今回はカテゴリ別集計の計算だけを、あえてCategoryChart.vue
自身のcomputedに持たせました。この集計結果を必要としているのがこのコンポーネントだけだったため、
「共通の元データは親で管理し、特定のコンポーネントだけが必要とする加工は、
そのコンポーネント自身にやらせる」という判断です。前作までとは少し違う設計を
あえて選んでみることで、どちらが読みやすいかを比較できました。
設計判断で意識したこと
グラフは外部ライブラリを使わず自作した
メモアプリではMarkdown変換にmarkedライブラリを使いましたが、
今回の横棒グラフは「カテゴリごとの金額を最大値に対する割合で棒の幅にする」
という単純な計算とCSSだけで実現できます。Chart.jsのような描画ライブラリを
追加する選択肢もありましたが、この程度のシンプルなグラフであれば
ライブラリを追加するコストの方が大きいと判断し、自作にとどめました。
「複雑な処理は任せる、単純な処理は自分で書く」という判断基準が、
メモアプリのMarkdownと今回のグラフとで対照的に表れた部分です。
カテゴリは自由入力ではなく選択式にした
「食費」と入力する日もあれば「食料品」と入力する日もある、といった
表記ゆれが起きると、本来同じカテゴリの支出が別々の項目として
グラフに分かれてしまい、集計の意味がなくなってしまいます。
あらかじめ固定のカテゴリ一覧から選ぶ方式にすることで、
後で正確に集計できるようにしました。
つまずいたところ
-
refとreactiveの使い分けに最初は戸惑いました。単一の値はref、
複数項目をまとめたオブジェクトはreactive、という基準を意識するまでに
何度か公式ドキュメントを読み返しました。 -
watchで配列の中身の変化を検知するのに{ deep: true }が必要だと知らず、
最初は自動保存が効かずに少しはまりました。Reactの感覚のまま「新しい配列を
作れば検知されるはず」と思い込んでいたのが原因でした。
まとめ
同じ「CRUD+集計」という課題を、ReactとVueという異なるフレームワークで
2度実装したことで、「フレームワークの文法」と「状態管理・設計の考え方」を
切り分けて理解できるようになりました。次はTypeScript化やテスト導入など、
品質面の強化に挑戦してみたいと考えています。