[Vue] Vue3 + Pinia + Axios

안치영·2023년 9월 7일
3

Vue

목록 보기
2/2

이번에 들어가게된 공공클라우드 프로젝트를 하게 되면서
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: [],
	}),
});

🔥 store를 생성할 때 중요 사항

  • store의 이름을 지을때는 use + ' ' + store 로 지어주어야 함.
  • camelCase로 작명
  • storeId는 해당 스토어의 id값을 뜻한다.
  • state에는 중앙에서 관리해야 할 데이터의 key value 초기값을 지정해주면 된다.

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기능을 구현할 수 있을것입니다.

지적사항이나 질문사항 있으면 언제든 댓글 남겨주세요!

0개의 댓글