토이 프로젝트 - Vue, Firebase로 서버리스 PWA 개발

19. Firestore로 PWA myBlog 개발 - 글수정

그랜파 개발자 2025. 2. 24. 05:04

블로그 글 수정

글쓴이는 자신의 글을 수정할 수 있습니다.
글의 상세 보기 페이지에 접속한 사용자가 글쓴이라면 ‘수정’ 버튼이 나타납니다.
수정 버튼을 누르면 글을 수정할 수 있는 EditView 컴포넌트로 이동하고
EdtiView 컴포넌트에서 글을 수정할 수 있습니다.

 

글 상세 보기에서 수정 버튼을 누르면 글 수정 페이지로 이동할 때 수정할 글은 가지고 가려고 합니다.

props는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달할 때 사용하는 Vue의 기능입니다.
부모 컴포넌트가 데이터를 제공하고, 자식 컴포넌트가 이를 받을 수 있습니다.

Vue Router를 사용할 때 props를 통해 라우트 매개변수(route params), 쿼리(query) 또는 정적인 데이터를 컴포넌트로 전달할 수 있습니다. 이는 데이터 전달을 단순화하고 컴포넌트의 테스트 가능성을 높이는 데 유용합니다.

Vue에서 router와 props를 활용한 컴포넌트 간 데이터 전달 방법

Vue에서는 Vue Router를 사용하여 페이지 이동과 함께 데이터를 전달할 수 있습니다.

params를 이용한 데이터 전달 (URL에 포함)

params는 query와 달리, 동적 라우트 매칭을 사용하여 데이터를 전달하는 방식입니다.

 

라우터 설정

// router.js
import { createRouter, createWebHistory } from "vue-router";
import ParentComponent from "@/components/ParentComponent.vue";
import ChildComponent from "@/components/ChildComponent.vue";

const routes = [
  { path: "/", component: ParentComponent },
  { path: "/child/:id", component: ChildComponent, props: true } // props 활성화
];

const router = createRouter({
  history: createWebHistory(),
  routes
});

export default router;
  • props: true를 설정하면 params 값을 props로 자동 전달

부모 컴포넌트에서 params로 데이터 전달

<!-- ParentComponent.vue -->
<template>
  <div>
    <h1>부모 컴포넌트</h1>
    <button @click="goToChild">자식 페이지 이동</button>
  </div>
</template>

<script>
export default {
  methods: {
    goToChild() {
      this.$router.push({ path: `/child/123` }); // id 전달
    }
  }
};
</script>

자식 컴포넌트에서 데이터 받기

<!-- ChildComponent.vue -->
<template>
  <div>
    <h2>자식 컴포넌트</h2>
    <p>받은 ID: {{ id }}</p>
  </div>
</template>

<script>
export default {
  props: ["id"], // params를 props로 받기
};
</script>
  • props를 통해 id를 직접 받을 수 있음

myBlog 글수정

글 상세 보기에서 글을 수정 컴포넌트로 전달 합니다.
글 수정의 경우 작성과 마찬가지로 사용자가 등록한 카테고리를 나타내어 카테고리를 변경하거나
저장된 글의 제목과 내용을 수정폼에 나타내어 수정할 수 있도록 합니다.

카테고리는 로그인할 때 미리 로드 되어 있으므로
페이지가 열릴 때에는 로드된 카테고리에서 카테고리 이름만 배열로 추출하여 셀렉트 박스에 넣습니다.

PostView에서 EditView로 router의 params를 이용하여 게시글 전달하고
수정 페이지가 열릴 때 카테고리와 수정할 글을 받아 화면에 표시합니다.
글을 수정 후 상세보기 페이지로 돌아가면
상세 보기 페이지가 열릴 때 해당 글을 다시 로드하므로
수정된 글이 상세보기에 나타납니다.

Router

PostView.vue

글수정

EditView 컴포넌트 script

EditView 컴포넌트 template

EditView 컴포넌트 script - 카테고리 로드

EditView 컴포넌트 script - 글수정

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

EditView.vue

<!-- src/views/EditView.vue -->
<template>
  <v-container>
    <v-card>
      <v-card-title style="font-size:1em">글 수정</v-card-title>

      <v-form class="pa-2 mt-n2" ref="form" v-model="valid">

        <v-select class="mt-2" v-model="editedPost.category" 
          :items="myCategories" label="카테고리를 선택하세요"></v-select>

        <v-text-field class="mt-n2" label="제목" v-model="editedPost.title" 
          :rules="[v => !!v || 'Title is required']"></v-text-field>
        <v-textarea class="mt-n2" label="내용" rows="6" style="overflow-y: hidden;" 
          v-model="editedPost.content" :rules="[v => !!v || 'Content is required']"></v-textarea>

        <div class="mt-n2" style="text-align: right">
          <v-btn :disabled="!valid" @click="submitPost">          
            수정 &nbsp; <v-icon>mdi-pencil</v-icon>
          </v-btn>
        </div>

        <div class="text-center">
          <v-progress-circular v-if="loading" indeterminate></v-progress-circular>
        </div>

      </v-form>
    </v-card>
  </v-container>
</template>

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

export default {
  props: {
    postId: {
      type: String,
      required: true,
    },
    post: {
      type: Object,
      required: true,
    },
  },
  data() {
    return {
      editedPost: { postId: this.postId, ...this.post }, // 초기값을 props로 전달된 post로 설정
      valid: false, // 유효성 검사
      myCategories: [],
      selectedCategory: null, // 선택된 카테고리
    };
  },
  computed: {
    ...mapState('auth', ['profile']),
    ...mapState('post', ['loading', 'categories']),
  },
  methods: {    
    ...mapActions('post', ['fetchCategories', 'updatePost']),
    async submitPost() {
      if (this.$refs.form.validate()) {
        await this.updatePost({id: this.postId, post: this.editedPost}); 
        // 업데이트 후 상세 보기 페이지로 돌아감       
        this.$router.push({ name: "Post", params: { id: this.postId } });
      }
    },
  },
  async mounted() {
    // 카테고리를 로드한다.
    await this.fetchCategories({userId: this.profile.userId});
    this.myCategories = this.categories.map(category => category.name);
  },
};
</script>