개발/FullStack(Vue,Spring-블로그)

Vue, Spring - 간단한 블로그 만들기 - 2 (글 작성, 글 수정 (1차 마무리))

잇(IT) 2023. 10. 3. 15:44
728x90

https://insoobaik.tistory.com/518

 

Vue, Spring - 간단한 블로그 만들기 - 2 (views 생성 및 Spring 데이터 요청 및 응답)

https://insoobaik.tistory.com/517 Vue, Spring - 간단한 블로그 만들기 - 1 (Vue 설정) - 실행 구조 1. Spring Boot - Spring Boot는 내장된 웹 서버(Tomcat 등)을 사용하여 백엔드 서버를 실행한다. - 애플리케이션을 실행

insoobaik.tistory.com

- 이전 블로그의 Home 페이지, Read 페이지에 이어 글을 작성하는(Write) 화면과, 글을 수정하는(Edit) 화면에 대한 작업을 할 것이다.


- Header.Vue의 코드 중

<template>
<el-header class="header">
<!--        <RouterLink to="/">Home</RouterLink>-->
<!--        <RouterLink to="/write">글 작성</RouterLink>-->
<el-menu mode="horizontal" router>
  <el-menu-item index="/">Home</el-menu-item>
  <el-menu-item index="/write">Write</el-menu-item>
</el-menu>
</el-header>
</template>

위 코드를 보게 되면 모든 화면에 공통적으로 적용되는 코드에는 2개의 메뉴가 있다.

 1. Home 화면으로 이동하는 메뉴

 2. Write 화면으로 이동하는 메뉴 

Write 메뉴의 경우 /write 경로로 이동하게 된다.

 

- /write 경로의 경우 router에 의해 path가 생성되어 있는 상태이다.


- 글 작성

- WriteView.vue

const title = ref("")
const content= ref("")

const router = useRouter();

const write = function () {
  axios.post("/api/posts",{
    title:title.value,
    content:content.value
  })
      .then(()=>{
        router.replace({name:"home"})
      });
}
</script>

<template>
  <div>
    <el-input v-model="title" placeholder="제목을 입력해주세요"/>
  </div>

  <div>
    <div class="mt-2">
      <el-input v-model="content" type="textarea" rows="15"/>
    </div>

    <div class="mt-2">
      <div class="d-flex justify-content-end">
        <el-button type="primary" @click="write()">글 작성완료</el-button>
      </div>
    </div>
  </div>
</template>

<style>

</style>

const title = ref("")
const content= ref("")

const router = useRouter();

- 반응형으로 값이 변할 때 마다 새로운 값을 화면에 렌더링 하기 위해 ref를 사용한다.

- title, content를 반응형 변수 2개를 생성해주고 Router를 사용하기 위해 useRouter()를 통해 router 객체를 생성한다.

 

const write = function () {
  axios.post("/api/posts",{
    title:title.value,
    content:content.value
  })
      .then(()=>{
        router.replace({name:"home"})
      });
}

- write 함수의 경우 axios를 통해 post 요청을 보내게된다. 대게 post 요청은 데이터 생성, 뎅터 업데이트 즉, 데이터 전송이 필요할 때 사용한다.

- post 요청을 통해 title, content 내용을 전달한다.

- post 요청이 완료된 이후에 router.replace에 의해 home path로 이동하고, replace이기 때문에 기록이 스택에 남지 않으므로 뒤로가기가 불가능해진다.

 

<template>
  <div>
    <el-input v-model="title" placeholder="제목을 입력해주세요"/>
  </div>

  <div>
    <div class="mt-2">
      <el-input v-model="content" type="textarea" rows="15"/>
    </div>

    <div class="mt-2">
      <div class="d-flex justify-content-end">
        <el-button type="primary" @click="write()">글 작성완료</el-button>
      </div>
    </div>
  </div>
</template>

- v-model=title에 의해 위에서 생성한 반응형 변수 title에 실시간으로 값이 입력된다.

- v-model은 주로 입력 폼과 함께 사용된다.(input, textarea, select...)

- 실시간 입력된 내용들은 '글 작성완료' 버튼이 눌리게 되면 write() 함수가 실행되고, 위에 생성한 함수에 의해 post 요청에 의해 해당 데이터가 백엔드로 전달된다.


- 백엔드 동작

- 버튼 클릭에 의해 write() 함수가 실행되고 axios를 통해 post 요청이 백엔드 서버로 전달되면,

@PostMapping("/posts")
    public void post(@RequestBody @Valid PostCreate request) {
        request.validate();
        postService.write(request);
    }

- PostCreate 클래스는 title, content 변수를 가지고 있기 때문에 프론트엔드에서 title, content의 데이터를 전달하면 객체에 매핑이될 것이다.

- 값을 전달 받은 request 인스턴스의 validate() 함수를 통해, 전달받은 내용에 대한 검증을 마지고, 검증이 완료되면, postService 클래스의 write() 함수의 파라미터에 request를 전달하여 실행한다.

 

public Post write(PostCreate postCreate) {
//        postCreate -> Entity
        Post post = Post.builder()
                .title(postCreate.getTitle())
                .content(postCreate.getContent())
                .build();
        return postRepository.save(post);
    }

- postService의 write 메서드는 PostCreate 참조형을 전달 받고, 전달 받은 postCreate 객체를 builder를 통해 Post 객체에 값을 주입하고, 값을 주입받아 새롭게 생성된 post 객체를 postRepository의 save() 메서드를 통해 DB에 저장하게 된다.

 


- 글 수정

- EditView.vue

<script setup lang="ts">

import {useRouter} from "vue-router";
import {ref} from "vue";
import axios from "axios";

const router = useRouter();

const post = ref({
  id:0,
  title:"",
  content:"",
})

const props = defineProps({
  postId:{
    type:[Number, String],
    required:true,
  }
})

axios.get(`/api/posts/${props.postId}`).then(response=>{
  post.value=response.data;
});

const edit = () =>{
  axios.patch(`/api/posts/${props.postId}`, post.value).then(()=>{
    router.replace({name:"home"})
  });
}



</script>

<template>
  <div>
    <el-input v-model="post.title"/>
  </div>

  <div>
    <div class="mt-2">
      <el-input v-model="post.content" type="textarea" rows="15"/>
    </div>

    <div class="mt-2 d-flex justify-content-end">
      <el-button type="warning" @click="edit()">수정 완료</el-button>
    </div>
  </div>
</template>

<style>

</style>

const router = useRouter();

const post = ref({
  id:0,
  title:"",
  content:"",
})

1. router 사용을 위한 useRouter()를 통해 객체를 생성한다.

2. 반응형 객체인 post를 생성하여 id, title, content 값을 초기화 한다.

 

const props = defineProps({
  postId:{
    type:[Number, String],
    required:true,
  }
})

- defineProps는 부모 컴포넌트로부터 받은 Props를 사용할 때 사용된다. Props는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는데 사용되며, defineProps를 사용하여 프롭스 값으 안전하게 가져올 수 있다.

- 현재 위의 Props는 부모로부터 전달받은 것이 아니라 자식 컴포넌트에 내에서 post 객체를 사용하고 있다 

 

axios.get(`/api/posts/${props.postId}`).then(response=>{
  post.value=response.data;
});

1. axios를 통해 get 요청을 보내고 요청을 보낸 뒤 then을 통해 응답 받은 값을 post 객체의 값에 매핑한한다.

2. response.data를 통해 전달받은 데이터를 post.value에 주입하게 되면 반응형이기 때문에 실시간으로 전달 받은 값이 화면에 렌더링 된다.

 

const edit = () =>{
  axios.patch(`/api/posts/${props.postId}`, post.value).then(()=>{
    router.replace({name:"home"})
  });
}

1. edit() 함수가 실행되면 axios에 의해 patch(수정) 요청이 전달되고, path : home으로 페이지를 이동하고, replace이기 때문에 뒤로가기가 적용되지 않는다.


- 백엔드 edit에서 요청이 들어온 경우

- edit 페이지에서 get과 patch 2가지 요청이 백엔드 서버로 전달된다.

 

1. get 요청
@GetMapping("/posts/{postId}")
    public PostResponse get(@PathVariable Long postId) {
        return postService.get(postId);
    }

- 쿼리 파라미터로 postId의 값이 전달된 get 요청일 경우 해당 postId를 @PathVariable로 받아 postService의 get() 메서드의 파라미터로 전달하여 실행시킨다.

- 반환값은 PostResponse를 JSON 형태로 변환하여 프론트엔드 서버로 전달한다.

public PostResponse get(Long postId) {
        Post post = postRepository.findById(postId)
                .orElseThrow(PostNotFound::new);
        return PostResponse.builder()
                .id(post.getId())
                .title(post.getTitle())
                .content(post.getContent())
                .build();
    }

- postService의 get() 메서드는 전달 받은 postId를 통해 DB로 부터 해당 데이터를 가져와 builder를 통해 새로운 PostResponse 객체를 생성한다.

- get() 메서드를 통해 생성된 PostResponse 객체는 controller를 통해 프론트엔드로 전달된다.

 

2. patch 요청
@PatchMapping("/posts/{postId}")
    public void edit(@PathVariable Long postId, @RequestBody @Valid PostEdit request) {
        postService.edit(postId, request);
    }

1. controller의 Patch가 실행되고, 프론트엔드에서 axios로 patch 요청을 전달 할 때, postId, post 객체를 전달하기 때문에 @PathVariable를 통해 postId를 전달받고, PostEdit 참조형의 인스턴스에 전달받은 post.value 값이 매핑된다.

2. 전달받은 postId와 request 객체는 edit() 메서드의 파라미터로 전달된다.

 

@Transactional
    //--------게시글 수정
    public void edit(Long id, PostEdit postEdit) {
        Post post = postRepository.findById(id)
                .orElseThrow(PostNotFound::new);

        PostEditor.PostEditorBuilder editorBuilder = post.toEditor();

        PostEditor postEditor = editorBuilder.title(postEdit.getTitle())
                .content(postEdit.getContent())
                .build();

        post.edit(postEditor);

    }

1. edit() 메서드는 전달받은 id를 통해 DB로 부터 데이터를 가져오고, builder 패턴을 이용하여, PostEditor 객체를 생성하고, 새롭게 생성된 PostEditor를 post 클래스의 edit() 메서드의 파라미터로 전달한다.

2. post 클래스의 edit() 메서드에 의해 전달받은 PostEditor의 값이 post 객체에 매핑되어 데이터가 수정된다.

 

 

728x90