💬
Nuxt3とFirebaseでスレッド作成機能を実装する方法
はじめに
この記事では、Nuxt3とFirebaseを使用してスレッド作成機能を実装する方法について説明します。Vue 3のComposition API、Piniaストア、Vuetify UIコンポーネントを活用した実装例を紹介します。
実装の概要
スレッド作成機能は以下の要素で構成されています:
-
ページコンポーネント (
app/pages/thread/create.vue) -
フォームコンポーネント (
app/components/ThreadForm.vue) -
ストア (
app/stores/thread.ts)
1. ページコンポーネントの実装
<script setup lang="ts">
const { createThread } = useThreadStore()
const error = ref('')
const handleSubmit = async (data: { title: string; description: string }) => {
const { error: threadError } = await createThread(
data.title,
data.description
)
if (threadError) {
error.value = threadError.message
return
}
await navigateTo('/thread')
}
</script>
<template>
<v-card class="mx-auto" max-width="600">
<v-card-item>
<v-card-title> スレッド作成 </v-card-title>
</v-card-item>
<v-card-item>
<v-alert
v-if="error"
type="error"
:text="error"
variant="tonal"
class="mb-4"
/>
<ThreadForm @submit="handleSubmit" />
</v-card-item>
</v-card>
</template>
ポイント
- エラーハンドリング: スレッド作成時にエラーが発生した場合、アラートで表示
- ナビゲーション: 作成成功後は自動的にスレッド一覧ページに遷移
- UI: Vuetifyのカードコンポーネントを使用してモダンなデザイン
2. フォームコンポーネントの実装
<script setup lang="ts">
const form = ref()
const title = ref('')
const description = ref('')
const titleRules = [(v: string) => !!v || 'タイトルは必須です']
const descriptionRules = [(v: string) => !!v || '概要は必須です']
const emit = defineEmits<{
submit: [data: { title: string; description: string }]
}>()
const submit = async () => {
const { valid } = await form.value.validate()
if (valid) {
emit('submit', {
title: title.value,
description: description.value,
})
}
}
</script>
<template>
<v-form ref="form" @submit.prevent="submit">
<v-text-field
v-model="title"
label="タイトル"
variant="underlined"
:rules="titleRules"
required
/>
<v-textarea
v-model="description"
label="概要"
variant="underlined"
:rules="descriptionRules"
required
rows="4"
/>
<v-btn color="primary" class="mt-4" block type="submit">作成</v-btn>
</v-form>
</template>
ポイント
- バリデーション: タイトルと概要の必須チェック
- イベント発火: 親コンポーネントにsubmitイベントを発火
- UI: Vuetifyのフォームコンポーネントで統一感のあるデザイン
3. ストアの実装
export interface Thread {
id: string
title: string
description: string
authorId: string
authorName: string
createdAt: Date
updatedAt: Date
}
export const useThreadStore = defineStore('thread', () => {
const { $firestore } = useNuxtApp()
const firestore = $firestore as Firestore | null
const threads = ref<Thread[]>([])
const loading = ref(false)
// スレッドを作成
const createThread = async (title: string, description: string) => {
if (!firestore) {
return {
thread: null,
error: new Error('Firestoreが初期化されていません'),
}
}
const authStore = useAuthStore()
const profileStore = useProfileStore()
if (!authStore.user || !profileStore.profile) {
return {
thread: null,
error: new Error(
'ユーザーが認証されていないか、プロフィールが設定されていません'
),
}
}
loading.value = true
try {
const threadData = {
title,
description,
authorId: authStore.user.uid,
authorName: profileStore.profile.nickname,
createdAt: serverTimestamp(),
updatedAt: serverTimestamp(),
}
const docRef = await addDoc(collection(firestore, 'threads'), threadData)
const newThread: Thread = {
id: docRef.id,
...threadData,
createdAt: new Date(),
updatedAt: new Date(),
}
threads.value.unshift(newThread)
return { thread: newThread, error: null }
} catch (error) {
return { thread: null, error: error as Error }
} finally {
loading.value = false
}
}
return {
threads,
loading,
createThread,
getThreads,
}
})
ポイント
- 型安全性: TypeScriptのインターフェースでデータ構造を定義
- エラーハンドリング: 各段階でのエラーチェックと適切なエラーメッセージ
- 認証チェック: ユーザー認証とプロフィール設定の確認
- リアルタイム更新: 作成後すぐにローカルストアを更新
4. 実装の流れ
- フォーム入力: ユーザーがタイトルと概要を入力
- バリデーション: 必須項目のチェック
- 認証確認: ユーザー認証とプロフィール設定の確認
- Firestore保存: スレッドデータをFirestoreに保存
- ローカル更新: ストアの状態を更新
- 画面遷移: スレッド一覧ページに遷移
5. 技術スタック
- フロントエンド: Nuxt3, Vue 3, TypeScript
- UI: Vuetify 3
- 状態管理: Pinia
- バックエンド: Firebase Firestore
- 認証: Firebase Authentication
まとめ
この実装では、モダンなVue.jsの機能を活用して、ユーザーフレンドリーなスレッド作成機能を構築しています。型安全性、エラーハンドリング、UI/UXの観点から、実用的なアプリケーションに必要な要素を網羅しています。
特に、Composition APIとPiniaストアの組み合わせにより、保守性の高いコードを実現しており、今後の機能拡張にも対応しやすい構造になっています。
Discussion