이번에 들어가게된 공공클라우드 프로젝트를 하게 되면서
Vite기반 Vue3 프레임워크를 사용하게 됐고 store로는 pinia를 사용하게 됐다.
위 환경의 프론트엔드의 기본인 CRUD를 간단한 예시를 들은 사용법 입니다.
pinia 설치
Vue3 프로젝트 생성할 때 pinia를 설치할지 말지 선택할 수 있다.
까먹고 선택하지 않았으면
npm i pinia
1. main.js에 import && Store 생성하기
사용하려는 프로젝트의 main.js 파일에 import를 해줘야 사용할 수 있다.
import '@/assets/css/main.css';
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from '@/App.vue';
const app = createApp(App);
app.use(createPinia());
app.mount('#app');
이제 store를 사용할 준비가 되었으니 생성을 하면 된다.
import { defineStore } from 'pinia';
export const usePostStore = defineStore('storeId',{
state: () => ({
contents: [],
}),
});
READ (GET)
CRUD중 가장 쉬운 Read 이다.
// postData.js
import { defineStore } from 'pinia';
import axios from 'axios';
const baseUrl = 'http://localhost:5000';
export const usePostStore = defineStore('storeId',{
state: () => ({
contents: [],
}),
// 함수 정의하는 곳
actions: {
async fetchContents(){
try{
const res = await axios.get(baseUrl); // 요청한 데이터를
this.contents = res.data; // 생성한 state에 대입
// res에 담겨오는 데이터 항목들을 보고 자유롭게 사용할 수 있다.
// ex)
// if(res.status === 200){
// alert('Success Message');
// }
} catch (err) {
console.error('Fetch ERROR!', err);
}
}
},
});
위처럼 사용하는게 기본 형식이지만, response로 오는 데이터중 일부만 사용 하려면 구조분해할당을 하면 된다.
// postData.js
import { defineStore } from 'pinia';
import axios from 'axios';
const baseUrl = 'http://localhost:5000';
export const usePostStore = defineStore('storeId',{
state: () => ({
contents: [],
}),
actions: {
async fetchContents(){
try{
const { data, status } = await axios.get(baseUrl); // 구조분해할당
this.contents = data;
if(status === 200){
alert('Success Message');
}
}
} catch (err) {
console.error('Fetch ERROR!', err);
}
}
},
});
store에서의 작업을 끝냈으면 component로 가서 데이터를 사용하면 된다.
// PostList.vue
<template>
<div v-for="item in contents" :key="item.id">
<div>{{ item.postId }}</div>
<div>{{ item.postTitle }}</div>
<div>{{ item.postDescription }}</div>
</div>
</template>
<script setup>
import { usePostStore } from '@/stores/postData';
import { storeToRefs } from 'pinia';
import { onMounted } from 'vue';
// store 를 쓰기위함
const store = usePostStore();
// store에 우리가 만든 state를 사용하고, api요청 함수를 사용하기 위해
const { contents } = storeToRefs(store);
const { fetchContents } = store;
// 페이지 실행시 데이터 GET
onMounted(() => {
fetchContents();
});
</script>
storeToRefs??
생성한 store에서의 state값을 구조분해할당으로 가져오게 되면 "데이터의 반응성"을 잃게된다.
그러므로 pinia에서 제공하는 함수인 'storeToRefs' 를 사용하면 반응성을 유지시키면서 데이터를 사용할 수 있다.
CREATE (POST)
// postData.js
import { defineStore } from 'pinia';
import axios from 'axios';
const baseUrl = 'http://localhost:5000';
export const usePostStore = defineStore('storeId',{
state: () => ({
contents: [],
}),
actions: {
async createContents(payload){
// payload는 매개변수로 전달받은 데이터를 의미한다.
try{
// post요청시에는 url과 전달할 데이터를 아래와 같이 전송한다.
const { data } = await axios.post(`${baseUrl}/posts`, payload);
// 받아온 데이터를 만들어놓은 state에 추가해준다
this.contents.push(data);
} catch (err) {
console.error('Post ERROR!', err);
}
}
},
});
이제 똑같이 component에서 사용하면 된다.
// PostForm.vue
<template>
<form>
<label>제목<input v-model="postData.postTitle" type="text" /></label><br />
<label>설명<input v-model="postData.postDescription" type="text" /></label>
<button @click.prevent="addPostData">제출</button>
</form>
</template>
<script setup>
import { usePostStore } from '@/stores/pollData';
import { ref } from 'vue';
// store에서 사용할 함수를 가져온다
const store = usePostStore();
const { createContents } = store;
// form 제출 시 서버에 전달할 데이터 ref를 만들어 주기
const postData = ref({
postTitle: '',
postDescription: '',
});
// 데이터를 추가하는 함수
const addPollData = () => {
createContents(postData.value);
// input에 입력된 값 초기화
postData.value.postTitle = '';
postData.value.postDescription = '';
};
</script>
DELETE
보통의 api에서 요구하는 항목이 삭제하려는 항목의 id값이기 때문에 제일 쉬운게 Delete라고 생각한다
// postData.js
import { defineStore } from 'pinia';
import axios from 'axios';
const baseUrl = 'http://localhost:5000';
export const usePostStore = defineStore('storeId',{
state: () => ({
contents: [],
}),
actions: {
async deleteContents(id){
try{
// 삭제요청 시 필요한건 보통 삭제할 데이터의 id가 기본임
await axios.delete(`${baseUrl}/posts/${id}`);
this.contents = this.contents.filter((prev) => prev.id !== id);
} catch (err) {
console.error('Post ERROR!', err);
}
}
},
});
// PostList.vue
<template>
<div v-for="item in contents" :key="item.id">
<div>{{ item.postId }}</div>
<div>{{ item.postTitle }}</div>
<div>{{ item.postDescription }}</div>
<button @click="deletePost(item.id)">삭제</button>
</div>
</template>
<script setup>
import { usePostStore } from '@/stores/postData';
import { storeToRefs } from 'pinia';
import { onMounted } from 'vue';
// store 를 쓰기위함
const store = usePostStore();
// store에 우리가 만든 state를 사용하고, api요청 함수를 사용하기 위해
const { contents } = storeToRefs(store);
const { fetchContents, deleteContents } = store;
// 페이지 실행시 데이터 GET
onMounted(() => {
fetchContents();
});
// 삭제버튼시 실행
// 삭제버튼 컴포넌트가 분리되는 경우에는 props로받은 id값을 사용하면 되고,
// get한 데이터와 같이 있는 경우에는 바로 해당 id를 매개변수로 넣으면 된다.
const deletePoll = (id) => {
const confirmed = window.confirm('삭제?');
if (confirmed) {
deleteContents(id);
}
};
</script>
UPDATE (PUT)
여러개의 게시글 중 클릭한 게시글만 수정되게 끔 하는 코드를 예시를 들었다.
// postData.js
import { defineStore } from 'pinia';
import axios from 'axios';
const baseUrl = 'http://localhost:5000';
export const usePostStore = defineStore('storeId',{
state: () => ({
contents: [],
isEditMode: false, // 수정 모드인지에 대한 상태값
selectedItem: null, // 선택된 데이터의 초기값
}),
actions: {
async updateContents(payload){
try{
// 어떤 데이터인지?(payload.id), 수정된 값이 무엇인지?(payload)
await axios.put(`${baseUrl}/posts/${payload.postId}`, payload)
// 상태 초기화
this.selectedItem = null;
this.isEditMode = false;
} catch (err) {
console.error('Post ERROR!', err);
}
},
// 선택될 데이터를 store에 정의한 선택된 데이터로 저장하는 함수
selectItem(item){
this.selectedItem = item;
},
// 수정모드 on off 함수
toggleEditMode(){
this.isEditMode = !this.isEditMode;
}
},
});
// PostList.vue
<template>
<div v-for="item in contents" :key="item.id">
<div v-if="selectedItem && isEditMode && selectedItem.postId === item.postId">
<div>{{ item.postId }}</div>
<input type="text" v-model="selectedItem.postTitle" /><br />
<input type="text" v-model="selectedItem.postDescription" /><br />
<button @click="cancelEdit">취소</button>
<button @click="updateSelectedItem">저장</button>
</div>
<div v-else>
<div>{{ item.postId }}</div>
<div>{{ item.postTitle }}</div>
<div>{{ item.postDescription }}</div>
<button @click="deletePost(item.id)">삭제</button>
<button @click="editItem(item)">수정</button>
</div>
</div>
</template>
<script setup>
import { usePostStore } from '@/stores/postData';
import { storeToRefs } from 'pinia';
import { onMounted } from 'vue';
const store = usePostStore();
const { fetchContents } = store;
const { contents, selectedItem, isEditMode } = storeToRefs(store);
const { updateContents, selectItem, toggleEditMode, fetchContents } = store;
// 페이지 실행시 데이터 GET
onMounted(() => {
fetchContents();
});
// 수정버튼 누르면 실행되는 함수
const editItem = (item) => {
// selectItem 함수로 인해 selectedItem의 값이 수정된다.
// (null로있던 초기값이 data로 바뀜)
selectItem(item);
// 모드 변경
toggleEditMode();
};
// 저장버튼 누르면 실행되는 함수
const updateSelectedItem = () => {
// selectedItem에 저장되어있는 값으로 api요청 보낸다.
updateContents(selectedItem.value);
};
// 취소버튼
const cancelEdit = () => {
// seletedItem, isEditMode를 초기값으로 돌림.
selectItem(null);
toggleEditMode();
};
</script>
여기까지 Vue3 + Pinia + Axios를 활용한 CRUD예제를 작성해봤다.
각자의 API 스펙마다 달라지겠지만, 큰 틀은 변하지 않는다.
보통으로 사용되는 형식을 알고 응용할 줄만 알면 쉽게 CRUD기능을 구현할 수 있을것입니다.
지적사항이나 질문사항 있으면 언제든 댓글 남겨주세요!