◾️ はじめに
Vue.jsを学習中のエンジニアです👩🏻💻
今回、Vueのライブラリを使用せずにカレンダーを作成中に発生したエラーについて記事を書こうと思います。カレンダーのライブラリを使用しない理由は、カレンダーを好みのデザインやカスタマイズを行う際に、ゼロから作成した方が操作しやすいと考えたからです。
◾️ データの渡し方
前提:
- 6×7の日曜日スタートのカレンダーを作成
- 当月が日曜から始まらない部分に前月を表示
- 当月最終日から42マス目までは次月を表示
◉ やりたいこと
『カレンダーの月が変更されると、その月ごとに日付を取得し、日付を管理するコンポーネントで表示できるようにする』
① TypeScriptファイルのuseYearMonthDate.ts
のallDatesでカレンダーの日付を取得
import { computed, ref } from 'vue'
export const useYearMonthDate = () => {
// exports
const targetMonth = ref<Date>(new Date())
const allDates = ref<Array<number>>([])
// private variables
const currentYear = computed(() => { // 現在の年を取得 })
const currentMonth = computed(() => { // 現在の月を取得 })
const currentMonthLastDate = computed(() => { // 月の最後の年月日を取得 })
const prevMonthLastDate = computed(() => { // 前月の最後の日付を取得 })
const currentMonthFirstDay = computed(() => { // 月の最初の曜日インデックスを取得 })
const nextMonthFirstDate = computed(() => { // 次月の最初の日付を取得 })
const moveMonth = (delta: number): void => {
// ここで選択された月を表示する処理を行う
getAllDate()
}
// 前月取得=========================
const getPrevMonthDate = (): Array<number> => {
const prevDates: Array<number> = []
// ここで前月を取得する処理を行う
return prevDates
}
// 当月取得=========================
const getCurrentMonthDate = (): Array<number> => {
const currentDates: Array<number> = []
// ここで当月を取得する処理を行う
return currentDates
}
// 次月取得=========================
const getNextDate = (prevDates: number): Array<number> => {
const nextDates: Array<number> = []
// ここで次月を取得する処理を行う
return nextDates
}
// 前月、当月、次月の配列を結合=========================
const getAllDate = () => {
const prevDates = getPrevMonthDate()
const currentDates = getCurrentMonthDate()
const nextDates = getNextDate(prevDates.length)
allDates.value = prevDates.concat(currentDates, nextDates)
}
getAllDate()
return {
targetMonth,
getAllDate,
moveMonth,
allDates
}
})
② 取得した日付データ(allDates)を表示したいコンポーネントSelectDate.vue
にデータを渡す
※ SelectDate.vue
:カレンダーの赤枠部分、日付を表示するコンポーネントのこと
<script setup lang="ts">
import { useYearMonthDate } from '@/composables/useYearMonthDate'
import { computed } from 'vue'
const { allDates } = useYearMonthDate()
const days = computed(() => allDates.value)
</script>
<template>
<div class="sd">
<div class="sd-row">
<span class="sd-column" v-for="(day, i) in days" :key="i">
{{ day }}
</span>
</div>
</div>
</template>
// スタイル部分に関しては省略
◉ 結果
データを渡す際に、SelectDate.vue
コンポーネントに必要なデータallDatesをインポート、computedを使用して再計算するだけでは、次月<
や前月>
を選択しても、はじめに表示されている日付から更新されませんでした。
※月を選択できるコンポーネントについての説明には今回関係がないため省きます。
◉ 問題点
- Vueのリアクティブな変更を検知できていない可能性
- データが変更されたときに再レンダリングされるはずが変更を検知せず、再レンダリングされなかった可能性が高い
◉ 解決策
defineStoreを使用し、storeToRefsでデータを取得することにより解決しました!
これにより、Vueのリアクティブな動作が実行され、SelectDate.vue
のビューが自動的に更新されるようになりました。
この挙動は、Pinia内部で行われています。Piniaは、Vueのリアクティブな性質を活用して、ストアの状態の変更を検知し、それに応じてコンポーネントを再レンダリングするためです。
※補足1:defineStoreとは
Vue.jsの状態管理ライブラリであるPiniaの関数のことです。
使用することで、アプリケーション全体で共有する状態(state)やその状態を操作するためのアクション(actions)、状態の計算プロパティ(getters)を定義することができます。
※補足2:storeToRefsを使用する理由
ストア全体を取得するとストアオブジェクト自体はリアクティブですが、ストアのプロパティに直接アクセスするとそのプロパティはリアクティブなプロパティとして扱われなくなってしまうためです。
そのため、リアクティブ性を失うことなく取り出すにはstoreToRefsを使用しています。
以下が変更箇所です。
import { computed, ref } from 'vue'
import { defineStore } from 'pinia'
export const useYearMonthDate = defineStore('useYearMonthDate', () => {
<script setup lang="ts">
import { useYearMonthDate } from '@/composables/useYearMonthDate'
import { storeToRefs } from 'pinia'
import { computed } from 'vue'
const { allDates } = storeToRefs(useYearMonthDate())
const days = computed(() => allDates.value)
</script>
◾️ 最後に
ここまで読んでくださってありがとうございます🙇🏻♀️
私と同様にVueで取得したデータを渡す点で悩んでいる方の少しでも役に立てたら嬉しいです!