그랜파 개발자의 프론트엔드 공부-Vue

Vue로 PWA 개발, ChatGPT의 프론트엔드 앱 CRUD 예제 워크플로우

그랜파 개발자 2024. 12. 2. 03:13

Vue로 PWA 개발 - 그랜파 개발자.

 

ChatGPT의 PWA 예제를 Router 예제에 추가하였습니다.

PWA예제가 단순히 Post List 화면 하나만 있어 페이지 라우팅 등이 필요해서 입니다. 앞으로 계속 페이지를 추가하면서 프로젝트를 진행할 예정입니다.

 

PWA 예제를 실행하여 봅시다.

프로젝트 만들기

1. Vue 프로젝트 만들기

vue create my-pwa-app

 

? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, PWA, Router, Vuex
? Choose a version of Vue.js that you want to start the project with 2.x
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? (y/N) n

 

2. vuetify 설치

cd my-pwa-app

vue add vuetify

 

? Choose a preset: (Use arrow keys)
Vuetify 2 - Configure Vue CLI (advanced)
> Vuetify 2 - Vue CLI (recommended)
Vuetify 2 - Prototype (rapid development)
Vuetify 3 - Vite (preview)
Vuetify 3 - Vue CLI (preview)

3. firebase 설치

npm install firebase

 

4. 실행

npm run serve

 

브라우저에서 http://localhost:8080으로 이동하면 앱을 볼 수 있습니다.

 

그림 29-1그림 29-2

PostList.vue 분석

<!-- src/views/PostList.vue -->
<template>
  <v-container>
    <v-btn @click="openDialog">글 작성하기</v-btn>

    <v-dialog v-model="dialog" max-width="500px">
      <v-card>
        <v-card-title>새 글 작성</v-card-title>
        <v-card-text>
          <v-text-field label="제목" v-model="newPost.title"></v-text-field>
          <v-textarea label="내용" v-model="newPost.content"></v-textarea>
        </v-card-text>
        <v-card-actions>
          <v-btn color="primary" @click="savePost">저장</v-btn>
          <v-btn color="secondary" @click="closeDialog">취소</v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>

    <v-list>
      <v-list-item v-for="post in posts" :key="post.id" @click="editPost(post)">
        <v-list-item-content>
          <v-list-item-title>{{ post.title }}</v-list-item-title>
          <v-list-item-subtitle>{{ post.content }}</v-list-item-subtitle>
        </v-list-item-content>
      </v-list-item>
    </v-list>
  </v-container>
</template>

<script>
import { mapActions, mapGetters } from "vuex";

export default {
  data() {
    return {
      dialog: false,
      newPost: {
        title: "",
        content: ""
      }
    };
  },
  computed: {
    ...mapGetters(["allPosts"]),
    posts() {
      return this.allPosts;
    }
  },
  methods: {
    ...mapActions(["fetchPosts", "addPost", "updatePost"]),
    openDialog() {
      this.newPost = { title: "", content: "" };
      this.dialog = true;
    },
    closeDialog() {
      this.dialog = false;
    },
    savePost() {
      if (this.newPost.id) {
        this.updatePost({ id: this.newPost.id, post: this.newPost });
      } else {
        this.addPost(this.newPost);
      }
      this.closeDialog();
    },
    editPost(post) {
      this.newPost = { ...post };
      this.dialog = true;
    }
  },
  created() {
    this.fetchPosts();
  }
};
</script>

1. 글 목록

created()는 Vue 인스턴스의 라이프사이클 훅 중 하나로, Vue 컴포넌트가 생성된 직후에 호출됩니다. DOM은 아직 렌더링되지 않은 상태로 여기서 fetchPosts()를 호출합니다.
fetchPosts()함수는 데이터베이스에 등록된 모든 Post를 가져오는 기능으로 데이터베이스에서 모든 Post를 가져와 state에 상태 변수 posts[]에 저장합니다.

src/store/index.js

async fetchPosts({ commit }) {
    // posts 컬렉션의 전체 문서를 가져옵니다.
    const querySnapshot = await getDocs(collection(db, "posts"));
    // 문서를 배열로 만들어
    const posts = querySnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
    // state의 상태 변수 posts[]에 저장합니다.
    commit("setPosts", posts);
}

 

이렇게 state의 상태 변수 posts[]에 저장된 문서들은 getters의 allPosts로 가져와 computed의 속성 함수 posts()로 접근이 가능합니다.

src/store/index.js의 getters

getters: {
    allPosts: state => state.posts
  }

PostList.vue 컴포넌트의 스크립트

computed: {
    ...mapGetters(["allPosts"]),
    posts() {
      return this.allPosts;
    }
}

PostList.vue 컴포넌트에서 posts 목록 나타내기

<v-list>
    <v-list-item v-for="post in posts" :key="post.id" @click="editPost(post)">
      <v-list-item-content>
        <v-list-item-title>{{ post.title }}</v-list-item-title>
        <v-list-item-subtitle>{{ post.content }}</v-list-item-subtitle>
      </v-list-item-content>
    </v-list-item>
 </v-list>

2. 새글 작성하기

새글 작성에는 두 가지가 있습니다.
‘글 작성하기’ 버튼을 눌러 새 글을 작성하는 경우와 글 목록에서 글을 선택하여 수정하는 경우입니다.
글을 작성하기 위한 데이터는 newPost: { title: "", content: "" } 입니다.
새 글은 이것이 공란으로 있지만, 글을 수정하는 경우는 이미 등록된 값으로 저장되어 있으므로 저장을 할 때 새 글을 저장하는 것인지, 등록된 글을 수정하는 것인지 알 수 있습니다.

 

글 작성하기 버튼을 누르면 제목과 내용이 공란으로 다이얼로그가 나타납니다.
글 목록에서 글을 선택하는 경우 제목과 내용이 등록된 내용으로 채워져서 다이얼로그가 나타납니다.
저장을 누르면 새 글의 등록( addPost()) 인지, 이미 등록된 글의 수정(UpdatePost())인지 구분하여 저장 또는 수정을 합니다.

 

코드를 봅시다.

새글 작성 버튼을 누르면 Dialog가 나타납니다.

PostList.vue 컴포넌트의 새글 작성 Dialog

<v-dialog v-model="dialog" max-width="500px">
    <v-card>
      <v-card-title>새 글 작성</v-card-title>
      <v-card-text>
        <v-text-field label="제목" v-model="newPost.title"></v-text-field>
        <v-textarea label="내용" v-model="newPost.content"></v-textarea>
      </v-card-text>
      <v-card-actions>
        <v-btn color="primary" @click="savePost">저장</v-btn>
        <v-btn color="secondary" @click="closeDialog">취소</v-btn>
      </v-card-actions>
    </v-card>
</v-dialog>

 

제목과 내용을 입력하고 저장을 누르면 포스트가 기존에 저장된 것일 경우 수정, 새 포스트인 경우 저장이 됩니다.

PostList.vue 컴포넌트의 스크립트

savePost() {
    if (this.newPost.id) {
      this.updatePost({ id: this.newPost.id, post: this.newPost });
    } else {
      this.addPost(this.newPost);
    }
 },

src/store/index.js

async addPost({ commit }, post) {
   const docRef = await addDoc(collection(db, "posts"), post);
   commit("addPost", { id: docRef.id, ...post });
},

async updatePost({ commit }, { id, post }) {
  const docRef = doc(db, "posts", id);
  await updateDoc(docRef, post);
  commit("updatePost", { id, ...post });
}

 

Vue 프로젝트 Beta Test : mylog, 일상의 기록